update libusb to 1.0.27 (#1259)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Tue, 16 Apr 2024 14:09:53 +0000 (08:09 -0600)
committerGitHub <noreply@github.com>
Tue, 16 Apr 2024 14:09:53 +0000 (08:09 -0600)
17 files changed:
mac/libusb/README
mac/libusb/Xcode/config.h
mac/libusb/core.c
mac/libusb/descriptor.c
mac/libusb/hotplug.c
mac/libusb/io.c
mac/libusb/libusb.h
mac/libusb/libusbi.h
mac/libusb/os/darwin_usb.c
mac/libusb/os/darwin_usb.h
mac/libusb/os/events_posix.c
mac/libusb/os/events_posix.h
mac/libusb/os/threads_posix.c
mac/libusb/strerror.c
mac/libusb/sync.c
mac/libusb/version.h
mac/libusb/version_nano.h

index 47aaf8106b7db2fceb1f9b6286a45c47f30cde2f..8e7ea725ac3ff51fce1e8a188823a085fc2f4890 100644 (file)
@@ -1,4 +1,4 @@
-This is libusb-1.0.26 from https://libusb.info/.
+This is libusb-1.0.27 from https://libusb.info/.
 Since we have such problems with people
 getting libusb successfully built - between the Universal Build issues
 and the fact that we have to work hard to go find where it's installed
index 59f3463352269488da3f41ff4064497dec29e6d1..c589a0fba73436a269abb107aff5fc37bb921335 100644 (file)
@@ -8,12 +8,6 @@
 /* Define to 1 to enable message logging. */
 #define ENABLE_LOGGING 1
 
-/* On 10.12 and later, use newly available clock_*() functions */
-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
-/* Define to 1 if you have the `clock_gettime' function. */
-#define HAVE_CLOCK_GETTIME 1
-#endif
-
 /* On 10.6 and later, use newly available pthread_threadid_np() function */
 #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
 /* Define to 1 if you have the 'pthread_threadid_np' function. */
index ec429b7cf17e8205fffb07e3acb1697535efcb73..ffe33b7758c4717397ed56d23b2ae68391e044a7 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */
 /*
  * Core functions for libusb
- * Copyright © 2012-2013 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * Copyright © 2012-2023 Nathan Hjelm <hjelmn@cs.unm.edu>
  * Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org>
  * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
  *
@@ -34,7 +34,7 @@
 
 static const struct libusb_version libusb_version_internal =
        { LIBUSB_MAJOR, LIBUSB_MINOR, LIBUSB_MICRO, LIBUSB_NANO,
-         LIBUSB_RC, "http://libusb.info" };
+         LIBUSB_RC, "https://libusb.info" };
 static struct timespec timestamp_origin;
 #if defined(ENABLE_LOGGING) && !defined(USE_SYSTEM_LOGGING_FACILITY)
 static libusb_log_cb log_handler;
@@ -43,6 +43,9 @@ static libusb_log_cb log_handler;
 struct libusb_context *usbi_default_context;
 struct libusb_context *usbi_fallback_context;
 static int default_context_refcnt;
+#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
+static usbi_atomic_t default_debug_level = -1;
+#endif
 static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER;
 static struct usbi_option default_context_options[LIBUSB_OPTION_MAX];
 
@@ -57,12 +60,12 @@ struct list_head active_contexts_list;
  *
  * libusb is an open source library that allows you to communicate with USB
  * devices from user space. For more info, see the
- * <a href="http://libusb.info">libusb homepage</a>.
+ * <a href="https://libusb.info">libusb homepage</a>.
  *
  * This documentation is aimed at application developers wishing to
  * communicate with USB peripherals from their own software. After reviewing
  * this documentation, feedback and questions can be sent to the
- * <a href="http://mailing-list.libusb.info">libusb-devel mailing list</a>.
+ * <a href="https://mailing-list.libusb.info">libusb-devel mailing list</a>.
  *
  * This documentation assumes knowledge of how to operate USB devices from
  * a software standpoint (descriptors, configurations, interfaces, endpoints,
@@ -111,17 +114,18 @@ struct list_head active_contexts_list;
  * libusb uses stderr for all logging. By default, logging is set to NONE,
  * which means that no output will be produced. However, unless the library
  * has been compiled with logging disabled, then any application calls to
- * libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level), or the setting of the
- * environmental variable LIBUSB_DEBUG outside of the application, can result
- * in logging being produced. Your application should therefore not close
- * stderr, but instead direct it to the null device if its output is
- * undesirable.
- *
- * The libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) function can be
- * used to enable logging of certain messages. Under standard configuration,
- * libusb doesn't really log much so you are advised to use this function
- * to enable all error/warning/ informational messages. It will help debug
- * problems with your software.
+ * libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level),
+ * libusb_init_context, or the setting of the environmental variable
+ * LIBUSB_DEBUG outside of the application, can result in logging being
+ * produced. Your application should therefore not close stderr, but instead
+ * direct it to the null device if its output is undesirable.
+ *
+ * The libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) or
+ * libusb_init_context functions can be used to enable logging of certain
+ * messages. With the default configuration, libusb will not log much so if
+ * you are advised to use one of these functions to enable all
+ * error/warning/informational messages. It will help debug problems with your
+ * software.
  *
  * The logged messages are unstructured. There is no one-to-one correspondence
  * between messages being logged and success or failure return codes from
@@ -137,19 +141,19 @@ struct list_head active_contexts_list;
  * The LIBUSB_DEBUG environment variable can be used to enable message logging
  * at run-time. This environment variable should be set to a log level number,
  * which is interpreted the same as the
- * libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) parameter. When this
- * environment variable is set, the message logging verbosity level is fixed
- * and libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) effectively does
- * nothing.
+ * libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level), or
+ * libusb_init_context(&ctx, &(struct libusb_init_option){.option = LIBUSB_OPTION_LOG_LEVEL, .value = {.ival = level}}, 0).
+ * When the environment variable is set, the message logging verbosity level is
+ * fixed and setting the LIBUSB_OPTION_LOG_LEVEL option has no effect.
  *
  * libusb can be compiled without any logging functions, useful for embedded
- * systems. In this case, libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level)
- * and the LIBUSB_DEBUG environment variable have no effects.
+ * systems. In this case, neither the LIBUSB_OPTION_LOG_LEVEL option, nor the
+ * LIBUSB_DEBUG environment variable will have any effect.
  *
  * libusb can also be compiled with verbose debugging messages always. When
  * the library is compiled in this way, all messages of all verbosities are
- * always logged. libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) and
- * the LIBUSB_DEBUG environment variable have no effects.
+ * always logged. Again, in this case, neither the LIBUSB_OPTION_LOG_LEVEL
+ * option, nor the LIBUSB_DEBUG environment variable will have any effect.
  *
  * \section remarks Other remarks
  *
@@ -327,23 +331,23 @@ if (cfg != desired)
  * developed modules may both use libusb.
  *
  * libusb is written to allow for these multiple user scenarios. The two
- * "instances" of libusb will not interfere: libusb_set_option() calls
- * from one user will not affect the same settings for other users, other
- * users can continue using libusb after one of them calls libusb_exit(), etc.
+ * "instances" of libusb will not interfere: an option set by one user will have
+ * no effect the same option for other users, other users can continue using
+ * libusb after one of them calls libusb_exit(), etc.
  *
  * This is made possible through libusb's <em>context</em> concept. When you
- * call libusb_init(), you are (optionally) given a context. You can then pass
+ * call libusb_init_context(), you are (optionally) given a context. You can then pass
  * this context pointer back into future libusb functions.
  *
  * In order to keep things simple for more simplistic applications, it is
  * legal to pass NULL to all functions requiring a context pointer (as long as
  * you're sure no other code will attempt to use libusb from the same process).
  * When you pass NULL, the default context will be used. The default context
- * is created the first time a process calls libusb_init() when no other
+ * is created the first time a process calls libusb_init_context() when no other
  * context is alive. Contexts are destroyed during libusb_exit().
  *
  * The default context is reference-counted and can be shared. That means that
- * if libusb_init(NULL) is called twice within the same process, the two
+ * if libusb_init_context(NULL, x, y) is called twice within the same process, the two
  * users end up sharing the same context. The deinitialization and freeing of
  * the default context will only happen when the last user calls libusb_exit().
  * In other words, the default context is created and initialized when its
@@ -413,6 +417,7 @@ if (cfg != desired)
   * - libusb_get_device_speed()
   * - libusb_get_iso_packet_buffer()
   * - libusb_get_iso_packet_buffer_simple()
+  * - libusb_get_max_alt_packet_size()
   * - libusb_get_max_iso_packet_size()
   * - libusb_get_max_packet_size()
   * - libusb_get_next_timeout()
@@ -436,6 +441,7 @@ if (cfg != desired)
   * - libusb_hotplug_deregister_callback()
   * - libusb_hotplug_register_callback()
   * - libusb_init()
+  * - libusb_init_context()
   * - libusb_interrupt_event_handler()
   * - libusb_interrupt_transfer()
   * - libusb_kernel_driver_active()
@@ -931,13 +937,13 @@ uint8_t API_EXPORTED libusb_get_port_number(libusb_device *dev)
 /** \ingroup libusb_dev
  * Get the list of all port numbers from root for the specified device
  *
- * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102
+ * Since version 1.0.16, \ref LIBUSBX_API_VERSION >= 0x01000102
  * \param dev a device
  * \param port_numbers the array that should contain the port numbers
  * \param port_numbers_len the maximum length of the array. As per the USB 3.0
  * specs, the current maximum limit for the depth is 7.
  * \returns the number of elements filled
- * \returns LIBUSB_ERROR_OVERFLOW if the array is too small
+ * \returns \ref LIBUSB_ERROR_OVERFLOW if the array is too small
  */
 int API_EXPORTED libusb_get_port_numbers(libusb_device *dev,
        uint8_t *port_numbers, int port_numbers_len)
@@ -1049,8 +1055,8 @@ static const struct libusb_endpoint_descriptor *find_endpoint(
  * \param dev a device
  * \param endpoint address of the endpoint in question
  * \returns the wMaxPacketSize value
- * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
- * \returns LIBUSB_ERROR_OTHER on other failure
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns \ref LIBUSB_ERROR_OTHER on other failure
  */
 int API_EXPORTED libusb_get_max_packet_size(libusb_device *dev,
        unsigned char endpoint)
@@ -1079,6 +1085,65 @@ out:
        return r;
 }
 
+static const struct libusb_endpoint_descriptor *find_alt_endpoint(
+       struct libusb_config_descriptor *config,
+       int iface_idx, int altsetting_idx, unsigned char endpoint)
+{
+       if (iface_idx >= config->bNumInterfaces) {
+               return NULL;
+       }
+
+       const struct libusb_interface *iface = &config->interface[iface_idx];
+
+       if (altsetting_idx >= iface->num_altsetting) {
+               return NULL;
+       }
+
+       const struct libusb_interface_descriptor *altsetting
+               = &iface->altsetting[altsetting_idx];
+       int ep_idx;
+
+       for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) {
+               const struct libusb_endpoint_descriptor *ep =
+                       &altsetting->endpoint[ep_idx];
+               if (ep->bEndpointAddress == endpoint)
+                       return ep;
+       }
+       return NULL;
+}
+
+static int get_endpoint_max_packet_size(libusb_device *dev,
+       const struct libusb_endpoint_descriptor *ep)
+{
+       struct libusb_ss_endpoint_companion_descriptor *ss_ep_cmp;
+       enum libusb_endpoint_transfer_type ep_type;
+       uint16_t val;
+       int r = 0;
+       int speed;
+
+       speed = libusb_get_device_speed(dev);
+       if (speed >= LIBUSB_SPEED_SUPER) {
+               r = libusb_get_ss_endpoint_companion_descriptor(dev->ctx, ep, &ss_ep_cmp);
+               if (r == LIBUSB_SUCCESS) {
+                       r = ss_ep_cmp->wBytesPerInterval;
+                       libusb_free_ss_endpoint_companion_descriptor(ss_ep_cmp);
+               }
+       }
+
+       /* If the device isn't a SuperSpeed device or retrieving the SS endpoint didn't worked. */
+       if (speed < LIBUSB_SPEED_SUPER || r < 0) {
+               val = ep->wMaxPacketSize;
+               ep_type = (enum libusb_endpoint_transfer_type) (ep->bmAttributes & 0x3);
+
+               r = val & 0x07ff;
+               if (ep_type == LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS
+                   || ep_type == LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT)
+                       r *= (1 + ((val >> 11) & 3));
+       }
+
+       return r;
+}
+
 /** \ingroup libusb_dev
  * Calculate the maximum packet size which a specific endpoint is capable is
  * sending or receiving in the duration of 1 microframe
@@ -1099,24 +1164,25 @@ out:
  * libusb_set_iso_packet_lengths() in order to set the length field of every
  * isochronous packet in a transfer.
  *
+ * This function only considers the first alternate setting of the interface.
+ * If the endpoint has different maximum packet sizes for different alternate
+ * settings, you probably want libusb_get_max_alt_packet_size() instead.
+ *
  * Since v1.0.3.
  *
  * \param dev a device
  * \param endpoint address of the endpoint in question
  * \returns the maximum packet size which can be sent/received on this endpoint
- * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
- * \returns LIBUSB_ERROR_OTHER on other failure
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns \ref LIBUSB_ERROR_OTHER on other failure
+ * \see libusb_get_max_alt_packet_size
  */
 int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev,
        unsigned char endpoint)
 {
        struct libusb_config_descriptor *config;
        const struct libusb_endpoint_descriptor *ep;
-       struct libusb_ss_endpoint_companion_descriptor *ss_ep_cmp;
-       enum libusb_endpoint_transfer_type ep_type;
-       uint16_t val;
        int r;
-       int speed;
 
        r = libusb_get_active_config_descriptor(dev, &config);
        if (r < 0) {
@@ -1131,26 +1197,68 @@ int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev,
                goto out;
        }
 
-       speed = libusb_get_device_speed(dev);
-       if (speed >= LIBUSB_SPEED_SUPER) {
-               r = libusb_get_ss_endpoint_companion_descriptor(dev->ctx, ep, &ss_ep_cmp);
-               if (r == LIBUSB_SUCCESS) {
-                       r = ss_ep_cmp->wBytesPerInterval;
-                       libusb_free_ss_endpoint_companion_descriptor(ss_ep_cmp);
-               }
-       }
+       r = get_endpoint_max_packet_size(dev, ep);
 
-       /* If the device isn't a SuperSpeed device or retrieving the SS endpoint didn't worked. */
-       if (speed < LIBUSB_SPEED_SUPER || r < 0) {
-               val = ep->wMaxPacketSize;
-               ep_type = (enum libusb_endpoint_transfer_type) (ep->bmAttributes & 0x3);
+out:
+       libusb_free_config_descriptor(config);
+       return r;
+}
 
-               r = val & 0x07ff;
-               if (ep_type == LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS
-                   || ep_type == LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT)
-                       r *= (1 + ((val >> 11) & 3));
+/** \ingroup libusb_dev
+ * Calculate the maximum packet size which a specific endpoint is capable of
+ * sending or receiving in the duration of 1 microframe
+ *
+ * Only the active configuration is examined. The calculation is based on the
+ * wMaxPacketSize field in the endpoint descriptor as described in section
+ * 9.6.6 in the USB 2.0 specifications.
+ *
+ * If acting on an isochronous or interrupt endpoint, this function will
+ * multiply the value found in bits 0:10 by the number of transactions per
+ * microframe (determined by bits 11:12). Otherwise, this function just
+ * returns the numeric value found in bits 0:10. For USB 3.0 device, it
+ * will attempts to retrieve the Endpoint Companion Descriptor to return
+ * wBytesPerInterval.
+ *
+ * This function is useful for setting up isochronous transfers, for example
+ * you might pass the return value from this function to
+ * libusb_set_iso_packet_lengths() in order to set the length field of every
+ * isochronous packet in a transfer.
+ *
+ * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A
+ *
+ * \param dev a device
+ * \param interface_number the <tt>bInterfaceNumber</tt> of the interface
+ * the endpoint belongs to
+ * \param alternate_setting the <tt>bAlternateSetting</tt> of the interface
+ * \param endpoint address of the endpoint in question
+ * \returns the maximum packet size which can be sent/received on this endpoint
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns \ref LIBUSB_ERROR_OTHER on other failure
+ * \see libusb_get_max_iso_packet_size
+ */
+int API_EXPORTED libusb_get_max_alt_packet_size(libusb_device *dev,
+       int interface_number, int alternate_setting, unsigned char endpoint)
+{
+       struct libusb_config_descriptor *config;
+       const struct libusb_endpoint_descriptor *ep;
+       int r;
+
+       r = libusb_get_active_config_descriptor(dev, &config);
+       if (r < 0) {
+               usbi_err(DEVICE_CTX(dev),
+                       "could not retrieve active config descriptor");
+               return LIBUSB_ERROR_OTHER;
+       }
+
+       ep = find_alt_endpoint(config, interface_number,
+               alternate_setting, endpoint);
+       if (!ep) {
+               r = LIBUSB_ERROR_NOT_FOUND;
+               goto out;
        }
 
+       r = get_endpoint_max_packet_size(dev, ep);
+
 out:
        libusb_free_config_descriptor(config);
        return r;
@@ -1209,10 +1317,10 @@ void API_EXPORTED libusb_unref_device(libusb_device *dev)
  * handle for the underlying device. The handle allows you to use libusb to
  * perform I/O on the device in question.
  *
- * Call libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY) before
- * libusb_init() if you want to skip enumeration of USB devices. In particular,
- * this might be needed on Android if you don't have authority to access USB
- * devices in general.
+ * Call libusb_init_context with the LIBUSB_OPTION_NO_DEVICE_DISCOVERY
+ * option if you want to skip enumeration of USB devices. In particular, this
+ * might be needed on Android if you don't have authority to access USB
+ * devices in general. Setting this option with libusb_set_option is deprecated.
  *
  * On Linux, the system device handle must be a valid file descriptor opened
  * on the device node.
@@ -1233,9 +1341,9 @@ void API_EXPORTED libusb_unref_device(libusb_device *dev)
  * \param dev_handle output location for the returned device handle pointer. Only
  * populated when the return code is 0.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NO_MEM on memory allocation failure
- * \returns LIBUSB_ERROR_ACCESS if the user has insufficient permissions
- * \returns LIBUSB_ERROR_NOT_SUPPORTED if the operation is not supported on this
+ * \returns \ref LIBUSB_ERROR_NO_MEM on memory allocation failure
+ * \returns \ref LIBUSB_ERROR_ACCESS if the user has insufficient permissions
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED if the operation is not supported on this
  * platform
  * \returns another LIBUSB_ERROR code on other failure
  */
@@ -1289,9 +1397,9 @@ int API_EXPORTED libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev,
  * \param dev_handle output location for the returned device handle pointer. Only
  * populated when the return code is 0.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NO_MEM on memory allocation failure
- * \returns LIBUSB_ERROR_ACCESS if the user has insufficient permissions
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NO_MEM on memory allocation failure
+ * \returns \ref LIBUSB_ERROR_ACCESS if the user has insufficient permissions
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 int API_EXPORTED libusb_open(libusb_device *dev,
@@ -1397,20 +1505,22 @@ static void do_close(struct libusb_context *ctx,
        for_each_transfer_safe(ctx, itransfer, tmp) {
                struct libusb_transfer *transfer =
                        USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+               uint32_t state_flags;
 
                if (transfer->dev_handle != dev_handle)
                        continue;
 
                usbi_mutex_lock(&itransfer->lock);
-               if (!(itransfer->state_flags & USBI_TRANSFER_DEVICE_DISAPPEARED)) {
+               state_flags = itransfer->state_flags;
+               usbi_mutex_unlock(&itransfer->lock);
+               if (!(state_flags & USBI_TRANSFER_DEVICE_DISAPPEARED)) {
                        usbi_err(ctx, "Device handle closed while transfer was still being processed, but the device is still connected as far as we know");
 
-                       if (itransfer->state_flags & USBI_TRANSFER_CANCELLING)
+                       if (state_flags & USBI_TRANSFER_CANCELLING)
                                usbi_warn(ctx, "A cancellation for an in-flight transfer hasn't completed but closing the device handle");
                        else
                                usbi_err(ctx, "A cancellation hasn't even been scheduled on the transfer for which the device is closing");
                }
-               usbi_mutex_unlock(&itransfer->lock);
 
                /* remove from the list of in-flight transfers and make sure
                 * we don't accidentally use the device handle in the future
@@ -1424,7 +1534,7 @@ static void do_close(struct libusb_context *ctx,
                 * the device handle is invalid
                 */
                usbi_dbg(ctx, "Removed transfer %p from the in-flight list because device handle %p closed",
-                        transfer, dev_handle);
+                        (void *) transfer, (void *) dev_handle);
        }
        usbi_mutex_unlock(&ctx->flying_transfers_lock);
 
@@ -1533,7 +1643,7 @@ libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle)
  * \param config output location for the bConfigurationValue of the active
  * configuration (only valid for return code 0)
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev_handle,
@@ -1584,7 +1694,7 @@ int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev_handle,
  * endpoint halts cleared, toggles reset).
  *
  * Not all backends support setting the configuration from user space, which
- * will be indicated by the return code LIBUSB_ERROR_NOT_SUPPORTED. As this
+ * will be indicated by the return code \ref LIBUSB_ERROR_NOT_SUPPORTED. As this
  * suggests that the platform is handling the device configuration itself,
  * this error should generally be safe to ignore.
  *
@@ -1615,11 +1725,11 @@ int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev_handle,
  * wish to activate, or -1 if you wish to put the device in an unconfigured
  * state
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the requested configuration does not exist
- * \returns LIBUSB_ERROR_BUSY if interfaces are currently claimed
- * \returns LIBUSB_ERROR_NOT_SUPPORTED if setting or changing the configuration
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the requested configuration does not exist
+ * \returns \ref LIBUSB_ERROR_BUSY if interfaces are currently claimed
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED if setting or changing the configuration
  * is not supported by the backend
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_set_auto_detach_kernel_driver()
  */
@@ -1653,10 +1763,10 @@ int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev_handle,
  * \param interface_number the <tt>bInterfaceNumber</tt> of the interface you
  * wish to claim
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the requested interface does not exist
- * \returns LIBUSB_ERROR_BUSY if another program or driver has claimed the
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the requested interface does not exist
+ * \returns \ref LIBUSB_ERROR_BUSY if another program or driver has claimed the
  * interface
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns a LIBUSB_ERROR code on other failure
  * \see libusb_set_auto_detach_kernel_driver()
  */
@@ -1699,8 +1809,8 @@ out:
  * \param interface_number the <tt>bInterfaceNumber</tt> of the
  * previously-claimed interface
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the interface was not claimed
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_set_auto_detach_kernel_driver()
  */
@@ -1744,9 +1854,9 @@ out:
  * \param alternate_setting the <tt>bAlternateSetting</tt> of the alternate
  * setting to activate
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed, or the
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the interface was not claimed, or the
  * requested alternate setting does not exist
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_handle,
@@ -1760,7 +1870,6 @@ int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_hand
                return LIBUSB_ERROR_INVALID_PARAM;
 
        if (!usbi_atomic_load(&dev_handle->dev->attached)) {
-               usbi_mutex_unlock(&dev_handle->lock);
                return LIBUSB_ERROR_NO_DEVICE;
        }
 
@@ -1787,8 +1896,8 @@ int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_hand
  * \param dev_handle a device handle
  * \param endpoint the endpoint to clear halt status
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
 int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev_handle,
@@ -1809,14 +1918,14 @@ int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev_handle,
  * If the reset fails, the descriptors change, or the previous state cannot be
  * restored, the device will appear to be disconnected and reconnected. This
  * means that the device handle is no longer valid (you should close it) and
- * rediscover the device. A return code of LIBUSB_ERROR_NOT_FOUND indicates
+ * rediscover the device. A return code of \ref LIBUSB_ERROR_NOT_FOUND indicates
  * when this is the case.
  *
  * This is a blocking function which usually incurs a noticeable delay.
  *
  * \param dev_handle a handle of the device to reset
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the
  * device has been disconnected
  * \returns another LIBUSB_ERROR code on other failure
  */
@@ -1881,7 +1990,7 @@ int API_EXPORTED libusb_alloc_streams(libusb_device_handle *dev_handle,
  * \param dev_handle a device handle
  * \param endpoints array of endpoints to free streams on
  * \param num_endpoints length of the endpoints array
- * \returns LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure
+ * \returns \ref LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure
  */
 int API_EXPORTED libusb_free_streams(libusb_device_handle *dev_handle,
        unsigned char *endpoints, int num_endpoints)
@@ -1944,7 +2053,7 @@ unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handl
  * \param dev_handle a device handle
  * \param buffer pointer to the previously allocated memory
  * \param length size of previously allocated memory
- * \returns LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure
+ * \returns \ref LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure
  */
 int API_EXPORTED libusb_dev_mem_free(libusb_device_handle *dev_handle,
        unsigned char *buffer, size_t length)
@@ -1966,8 +2075,8 @@ int API_EXPORTED libusb_dev_mem_free(libusb_device_handle *dev_handle,
  * \param interface_number the interface to check
  * \returns 0 if no kernel driver is active
  * \returns 1 if a kernel driver is active
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
  * is not available
  * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_detach_kernel_driver()
@@ -1997,15 +2106,15 @@ int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev_handle,
  *
  * Note that libusb itself also talks to the device through a special kernel
  * driver, if this driver is already attached to the device, this call will
- * not detach it and return LIBUSB_ERROR_NOT_FOUND.
+ * not detach it and return \ref LIBUSB_ERROR_NOT_FOUND.
  *
  * \param dev_handle a device handle
  * \param interface_number the interface to detach the driver from
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active
- * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if no kernel driver was active
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the interface does not exist
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
  * is not available
  * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_kernel_driver_active()
@@ -2036,12 +2145,12 @@ int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev_handle,
  * \param dev_handle a device handle
  * \param interface_number the interface to attach the driver from
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active
- * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if no kernel driver was active
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the interface does not exist
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
  * is not available
- * \returns LIBUSB_ERROR_BUSY if the driver cannot be attached because the
+ * \returns \ref LIBUSB_ERROR_BUSY if the driver cannot be attached because the
  * interface is claimed by a program or driver
  * \returns another LIBUSB_ERROR code on other failure
  * \see libusb_kernel_driver_active()
@@ -2072,14 +2181,14 @@ int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev_handle,
  * handles by default.
  *
  * On platforms which do not have LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER
- * this function will return LIBUSB_ERROR_NOT_SUPPORTED, and libusb will
+ * this function will return \ref LIBUSB_ERROR_NOT_SUPPORTED, and libusb will
  * continue as if this function was never called.
  *
  * \param dev_handle a device handle
  * \param enable whether to enable or disable auto kernel driver detachment
  *
- * \returns LIBUSB_SUCCESS on success
- * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
+ * \returns \ref LIBUSB_SUCCESS on success
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality
  * is not available
  * \see libusb_claim_interface()
  * \see libusb_release_interface()
@@ -2096,20 +2205,34 @@ int API_EXPORTED libusb_set_auto_detach_kernel_driver(
 }
 
 /** \ingroup libusb_lib
- * \deprecated Use libusb_set_option() instead using the
- * \ref LIBUSB_OPTION_LOG_LEVEL option.
+ * Deprecated. Use libusb_set_option() or libusb_init_context() instead,
+ * with the \ref LIBUSB_OPTION_LOG_LEVEL option.
  */
 void API_EXPORTED libusb_set_debug(libusb_context *ctx, int level)
 {
-#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
-       ctx = usbi_get_context(ctx);
-       if (!ctx->debug_fixed) {
-               level = CLAMP(level, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG);
-               ctx->debug = (enum libusb_log_level)level;
+       libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level);
+}
+
+static void libusb_set_log_cb_internal(libusb_context *ctx, libusb_log_cb cb,
+                                      int mode)
+{
+#if defined(ENABLE_LOGGING) && (!defined(ENABLE_DEBUG_LOGGING) || !defined(USE_SYSTEM_LOGGING_FACILITY))
+#if !defined(USE_SYSTEM_LOGGING_FACILITY)
+       if (mode & LIBUSB_LOG_CB_GLOBAL)
+               log_handler = cb;
+#endif
+#if !defined(ENABLE_DEBUG_LOGGING)
+       if (mode & LIBUSB_LOG_CB_CONTEXT) {
+               ctx = usbi_get_context(ctx);
+               ctx->log_handler = cb;
        }
 #else
        UNUSED(ctx);
-       UNUSED(level);
+#endif
+#else
+       UNUSED(ctx);
+       UNUSED(cb);
+       UNUSED(mode);
 #endif
 }
 
@@ -2139,24 +2262,7 @@ void API_EXPORTED libusb_set_debug(libusb_context *ctx, int level)
 void API_EXPORTED libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb,
        int mode)
 {
-#if defined(ENABLE_LOGGING) && (!defined(ENABLE_DEBUG_LOGGING) || !defined(USE_SYSTEM_LOGGING_FACILITY))
-#if !defined(USE_SYSTEM_LOGGING_FACILITY)
-       if (mode & LIBUSB_LOG_CB_GLOBAL)
-               log_handler = cb;
-#endif
-#if !defined(ENABLE_DEBUG_LOGGING)
-       if (mode & LIBUSB_LOG_CB_CONTEXT) {
-               ctx = usbi_get_context(ctx);
-               ctx->log_handler = cb;
-       }
-#else
-       UNUSED(ctx);
-#endif
-#else
-       UNUSED(ctx);
-       UNUSED(cb);
-       UNUSED(mode);
-#endif
+       libusb_set_log_cb_internal(ctx, cb, mode);
 }
 
 /** \ingroup libusb_lib
@@ -2176,72 +2282,95 @@ void API_EXPORTED libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb,
  * \param option which option to set
  * \param ... any required arguments for the specified option
  *
- * \returns LIBUSB_SUCCESS on success
- * \returns LIBUSB_ERROR_INVALID_PARAM if the option or arguments are invalid
- * \returns LIBUSB_ERROR_NOT_SUPPORTED if the option is valid but not supported
+ * \returns \ref LIBUSB_SUCCESS on success
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the option or arguments are invalid
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED if the option is valid but not supported
  * on this platform
- * \returns LIBUSB_ERROR_NOT_FOUND if LIBUSB_OPTION_USE_USBDK is valid on this platform but UsbDk is not available
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if LIBUSB_OPTION_USE_USBDK is valid on this platform but UsbDk is not available
  */
-int API_EXPORTED libusb_set_option(libusb_context *ctx,
+int API_EXPORTEDV libusb_set_option(libusb_context *ctx,
        enum libusb_option option, ...)
 {
        int arg = 0, r = LIBUSB_SUCCESS;
+       libusb_log_cb log_cb = NULL;
        va_list ap;
+#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
+       int is_default_context = (NULL == ctx);
+#endif
 
        va_start(ap, option);
+
        if (LIBUSB_OPTION_LOG_LEVEL == option) {
                arg = va_arg(ap, int);
                if (arg < LIBUSB_LOG_LEVEL_NONE || arg > LIBUSB_LOG_LEVEL_DEBUG) {
                        r = LIBUSB_ERROR_INVALID_PARAM;
                }
        }
-       va_end(ap);
-
-       if (LIBUSB_SUCCESS != r) {
-               return r;
+       if (LIBUSB_OPTION_LOG_CB == option) {
+               log_cb = (libusb_log_cb) va_arg(ap, libusb_log_cb);
        }
 
-       if (option >= LIBUSB_OPTION_MAX) {
-               return LIBUSB_ERROR_INVALID_PARAM;
-       }
+       do {
+               if (LIBUSB_SUCCESS != r) {
+                       break;
+               }
 
-       if (NULL == ctx) {
-               usbi_mutex_static_lock(&default_context_lock);
-               default_context_options[option].is_set = 1;
-               if (LIBUSB_OPTION_LOG_LEVEL == option) {
-                       default_context_options[option].arg.ival = arg;
+               if (option >= LIBUSB_OPTION_MAX) {
+                       r = LIBUSB_ERROR_INVALID_PARAM;
+                       break;
                }
-               usbi_mutex_static_unlock(&default_context_lock);
-       }
 
-       ctx = usbi_get_context(ctx);
-       if (NULL == ctx) {
-               return LIBUSB_SUCCESS;
-       }
+               if (NULL == ctx) {
+                       usbi_mutex_static_lock(&default_context_lock);
+                       default_context_options[option].is_set = 1;
+                       if (LIBUSB_OPTION_LOG_LEVEL == option) {
+                               default_context_options[option].arg.ival = arg;
+                       } else if (LIBUSB_OPTION_LOG_CB == option) {
+                               default_context_options[option].arg.log_cbval = log_cb;
+                               libusb_set_log_cb_internal(NULL, log_cb, LIBUSB_LOG_CB_GLOBAL);
+                       }
+                       usbi_mutex_static_unlock(&default_context_lock);
+               }
 
-       switch (option) {
-       case LIBUSB_OPTION_LOG_LEVEL:
+               ctx = usbi_get_context(ctx);
+               if (NULL == ctx)
+                       break;
+
+               switch (option) {
+               case LIBUSB_OPTION_LOG_LEVEL:
 #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
-               if (!ctx->debug_fixed)
-                       ctx->debug = (enum libusb_log_level)arg;
+                       if (!ctx->debug_fixed) {
+                               ctx->debug = (enum libusb_log_level)arg;
+                               if (is_default_context)
+                                       usbi_atomic_store(&default_debug_level, CLAMP(arg, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG));
+                       }
 #endif
-               break;
+                       break;
 
-               /* Handle all backend-specific options here */
-       case LIBUSB_OPTION_USE_USBDK:
-       case LIBUSB_OPTION_NO_DEVICE_DISCOVERY:
-               if (usbi_backend.set_option)
-                       return usbi_backend.set_option(ctx, option, ap);
+                       /* Handle all backend-specific options here */
+               case LIBUSB_OPTION_USE_USBDK:
+               case LIBUSB_OPTION_NO_DEVICE_DISCOVERY:
+                       if (usbi_backend.set_option) {
+                               r = usbi_backend.set_option(ctx, option, ap);
+                               break;
+                       }
 
-               return LIBUSB_ERROR_NOT_SUPPORTED;
-               break;
+                       r = LIBUSB_ERROR_NOT_SUPPORTED;
+                       break;
 
-       case LIBUSB_OPTION_MAX:
-       default:
-               return LIBUSB_ERROR_INVALID_PARAM;
-       }
+               case LIBUSB_OPTION_LOG_CB:
+                       libusb_set_log_cb_internal(ctx, log_cb, LIBUSB_LOG_CB_CONTEXT);
+                       break;
 
-       return LIBUSB_SUCCESS;;
+               case LIBUSB_OPTION_MAX: /* unreachable */
+               default:
+                       r = LIBUSB_ERROR_INVALID_PARAM;
+               }
+       } while (0);
+
+       va_end(ap);
+
+       return r;
 }
 
 #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
@@ -2264,20 +2393,35 @@ static enum libusb_log_level get_env_debug_level(void)
 }
 #endif
 
+/** \ingroup libusb_lib
+ * Deprecated initialization function. Equivalent to calling libusb_init_context with no options.
+ *
+ * \see libusb_init_context
+ */
+int API_EXPORTED libusb_init(libusb_context **ctx)
+{
+       return libusb_init_context(ctx, NULL, 0);
+}
+
 /** \ingroup libusb_lib
  * Initialize libusb. This function must be called before calling any other
  * libusb function.
  *
  * If you do not provide an output location for a context pointer, a default
  * context will be created. If there was already a default context, it will
- * be reused (and nothing will be initialized/reinitialized).
+ * be reused (and nothing will be initialized/reinitialized and options will
+ * be ignored). If num_options is 0 then options is ignored and may be NULL.
+ *
+ * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A
  *
  * \param ctx Optional output location for context pointer.
  * Only valid on return code 0.
+ * \param options Optional array of options to set on the new context.
+ * \param num_options Number of elements in the options array.
  * \returns 0 on success, or a LIBUSB_ERROR code on failure
  * \see libusb_contexts
  */
-int API_EXPORTED libusb_init(libusb_context **ctx)
+int API_EXPORTED libusb_init_context(libusb_context **ctx, const struct libusb_init_option options[], int num_options)
 {
        size_t priv_size = usbi_backend.context_priv_size;
        struct libusb_context *_ctx;
@@ -2293,10 +2437,12 @@ int API_EXPORTED libusb_init(libusb_context **ctx)
        }
 
        /* check for first init */
+       usbi_mutex_static_lock(&active_contexts_lock);
        if (!active_contexts_list.next) {
                list_init(&active_contexts_list);
                usbi_get_monotonic_time(&timestamp_origin);
        }
+       usbi_mutex_static_unlock(&active_contexts_lock);
 
        _ctx = calloc(1, PTR_ALIGN(sizeof(*_ctx)) + priv_size);
        if (!_ctx) {
@@ -2305,13 +2451,13 @@ int API_EXPORTED libusb_init(libusb_context **ctx)
        }
 
 #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
-       if (NULL == ctx && default_context_options[LIBUSB_OPTION_LOG_LEVEL].is_set) {
-               _ctx->debug = default_context_options[LIBUSB_OPTION_LOG_LEVEL].arg.ival;
-       } else {
+       _ctx->debug = LIBUSB_LOG_LEVEL_NONE;
+       if (getenv("LIBUSB_DEBUG")) {
                _ctx->debug = get_env_debug_level();
-       }
-       if (_ctx->debug != LIBUSB_LOG_LEVEL_NONE)
                _ctx->debug_fixed = 1;
+       } else if (default_context_options[LIBUSB_OPTION_LOG_LEVEL].is_set) {
+               _ctx->debug = default_context_options[LIBUSB_OPTION_LOG_LEVEL].arg.ival;
+       }
 #endif
 
        usbi_mutex_init(&_ctx->usb_devs_lock);
@@ -2324,7 +2470,29 @@ int API_EXPORTED libusb_init(libusb_context **ctx)
                if (LIBUSB_OPTION_LOG_LEVEL == option || !default_context_options[option].is_set) {
                        continue;
                }
-               r = libusb_set_option(_ctx, option);
+               if (LIBUSB_OPTION_LOG_CB != option) {
+                       r = libusb_set_option(_ctx, option);
+               } else {
+                       r = libusb_set_option(_ctx, option, default_context_options[option].arg.log_cbval);
+               }
+               if (LIBUSB_SUCCESS != r)
+                       goto err_free_ctx;
+       }
+
+       /* apply any options provided by the user */
+       for (int i = 0 ; i < num_options ; ++i) {
+               switch(options[i].option) {
+               case LIBUSB_OPTION_LOG_CB:
+                       r = libusb_set_option(_ctx, options[i].option, options[i].value.log_cbval);
+                       break;
+
+               case LIBUSB_OPTION_LOG_LEVEL:
+               case LIBUSB_OPTION_USE_USBDK:
+               case LIBUSB_OPTION_NO_DEVICE_DISCOVERY:
+               case LIBUSB_OPTION_MAX:
+               default:
+                       r = libusb_set_option(_ctx, options[i].option, options[i].value.ival);
+               }
                if (LIBUSB_SUCCESS != r)
                        goto err_free_ctx;
        }
@@ -2333,6 +2501,9 @@ int API_EXPORTED libusb_init(libusb_context **ctx)
        if (!ctx) {
                usbi_default_context = _ctx;
                default_context_refcnt = 1;
+#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
+               usbi_atomic_store(&default_debug_level, _ctx->debug);
+#endif
                usbi_dbg(usbi_default_context, "created default context");
        }
 
@@ -2360,8 +2531,12 @@ int API_EXPORTED libusb_init(libusb_context **ctx)
                *ctx = _ctx;
 
                if (!usbi_fallback_context) {
+#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
+                       if (usbi_atomic_load(&default_debug_level) == -1)
+                               usbi_atomic_store(&default_debug_level, _ctx->debug);
+#endif
                        usbi_fallback_context = _ctx;
-                       usbi_warn(usbi_fallback_context, "installing new context as implicit default");
+                       usbi_dbg(usbi_fallback_context, "installing new context as implicit default");
                }
        }
 
@@ -2432,6 +2607,9 @@ void API_EXPORTED libusb_exit(libusb_context *ctx)
        list_del(&_ctx->list);
        usbi_mutex_static_unlock(&active_contexts_lock);
 
+       /* Exit hotplug before backend dependency */
+       usbi_hotplug_exit(_ctx);
+
        if (usbi_backend.exit)
                usbi_backend.exit(_ctx);
 
@@ -2445,7 +2623,6 @@ void API_EXPORTED libusb_exit(libusb_context *ctx)
        /* Don't bother with locking after this point because unless there is
         * an application bug, nobody will be accessing the context. */
 
-       usbi_hotplug_exit(_ctx);
        usbi_io_exit(_ctx);
 
        for_each_device(_ctx, dev) {
@@ -2465,7 +2642,7 @@ void API_EXPORTED libusb_exit(libusb_context *ctx)
 
 /** \ingroup libusb_misc
  * Check at runtime if the loaded library has a given capability.
- * This call should be performed after \ref libusb_init(), to ensure the
+ * This call should be performed after \ref libusb_init_context(), to ensure the
  * backend has updated its capability set.
  *
  * \param capability the \ref libusb_capability to check for
@@ -2584,13 +2761,14 @@ static void log_v(struct libusb_context *ctx, enum libusb_log_level level,
        UNUSED(ctx);
 #else
        enum libusb_log_level ctx_level;
+       long default_level_value;
 
-       ctx = ctx ? ctx : usbi_default_context;
-       ctx = ctx ? ctx : usbi_fallback_context;
-       if (ctx)
+       if (ctx) {
                ctx_level = ctx->debug;
-       else
-               ctx_level = get_env_debug_level();
+       } else {
+               default_level_value = usbi_atomic_load(&default_debug_level);
+               ctx_level = default_level_value < 0 ? get_env_debug_level() : (enum libusb_log_level)default_level_value;
+       }
 
        if (ctx_level < level)
                return;
index 253ef1c319ef48f3a7fdbfe7820ff01e3105d376..4623ad1e35636c1abe84ba92c7af171445eb82ff 100644 (file)
@@ -521,7 +521,7 @@ static int get_config_descriptor(struct libusb_device *dev, uint8_t config_idx,
  *
  * This is a non-blocking function; the device descriptor is cached in memory.
  *
- * Note since libusb-1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, this
+ * Note since libusb-1.0.16, \ref LIBUSBX_API_VERSION >= 0x01000102, this
  * function always succeeds.
  *
  * \param dev the device
@@ -548,7 +548,7 @@ int API_EXPORTED libusb_get_device_descriptor(libusb_device *dev,
  * valid if 0 was returned. Must be freed with libusb_free_config_descriptor()
  * after use.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state
  * \returns another LIBUSB_ERROR code on error
  * \see libusb_get_config_descriptor
  */
@@ -588,7 +588,7 @@ int API_EXPORTED libusb_get_active_config_descriptor(libusb_device *dev,
  * valid if 0 was returned. Must be freed with libusb_free_config_descriptor()
  * after use.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
  * \returns another LIBUSB_ERROR code on error
  * \see libusb_get_active_config_descriptor()
  * \see libusb_get_config_descriptor_by_value()
@@ -634,7 +634,7 @@ int API_EXPORTED libusb_get_config_descriptor(libusb_device *dev,
  * valid if 0 was returned. Must be freed with libusb_free_config_descriptor()
  * after use.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
  * \returns another LIBUSB_ERROR code on error
  * \see libusb_get_active_config_descriptor()
  * \see libusb_get_config_descriptor()
@@ -699,7 +699,7 @@ void API_EXPORTED libusb_free_config_descriptor(
  * descriptor. Only valid if 0 was returned. Must be freed with
  * libusb_free_ss_endpoint_companion_descriptor() after use.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
  * \returns another LIBUSB_ERROR code on error
  */
 int API_EXPORTED libusb_get_ss_endpoint_companion_descriptor(
@@ -840,7 +840,7 @@ static int parse_bos(struct libusb_context *ctx,
  * \param bos output location for the BOS descriptor. Only valid if 0 was returned.
  * Must be freed with \ref libusb_free_bos_descriptor() after use.
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the device doesn't have a BOS descriptor
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the device doesn't have a BOS descriptor
  * \returns another LIBUSB_ERROR code on error
  */
 int API_EXPORTED libusb_get_bos_descriptor(libusb_device_handle *dev_handle,
@@ -1070,6 +1070,70 @@ void API_EXPORTED libusb_free_container_id_descriptor(
        free(container_id);
 }
 
+/** \ingroup libusb_desc
+ * Get a platform descriptor
+ *
+ * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A
+ *
+ * \param ctx the context to operate on, or NULL for the default context
+ * \param dev_cap Device Capability descriptor with a bDevCapabilityType of
+ * \ref libusb_capability_type::LIBUSB_BT_PLATFORM_DESCRIPTOR
+ * LIBUSB_BT_PLATFORM_DESCRIPTOR
+ * \param platform_descriptor output location for the Platform descriptor.
+ * Only valid if 0 was returned. Must be freed with
+ * libusb_free_platform_descriptor() after use.
+ * \returns 0 on success
+ * \returns a LIBUSB_ERROR code on error
+ */
+int API_EXPORTED libusb_get_platform_descriptor(libusb_context *ctx,
+       struct libusb_bos_dev_capability_descriptor *dev_cap,
+       struct libusb_platform_descriptor **platform_descriptor)
+{
+       struct libusb_platform_descriptor *_platform_descriptor;
+
+       if (dev_cap->bDevCapabilityType != LIBUSB_BT_PLATFORM_DESCRIPTOR) {
+               usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)",
+                        dev_cap->bDevCapabilityType,
+                        LIBUSB_BT_PLATFORM_DESCRIPTOR);
+               return LIBUSB_ERROR_INVALID_PARAM;
+       } else if (dev_cap->bLength < LIBUSB_BT_PLATFORM_DESCRIPTOR_MIN_SIZE) {
+               usbi_err(ctx, "short dev-cap descriptor read %u/%d",
+                        dev_cap->bLength, LIBUSB_BT_PLATFORM_DESCRIPTOR_MIN_SIZE);
+               return LIBUSB_ERROR_IO;
+       }
+
+       _platform_descriptor = malloc(dev_cap->bLength);
+       if (!_platform_descriptor)
+               return LIBUSB_ERROR_NO_MEM;
+
+       parse_descriptor(dev_cap, "bbbbu", _platform_descriptor);
+
+       /* Capability data is located after reserved byte and 128-bit UUID */
+       uint8_t* capability_data = dev_cap->dev_capability_data + 1 + 16;
+
+       /* Capability data length is total descriptor length minus initial fields */
+       size_t capability_data_length = _platform_descriptor->bLength - (16 + 4);
+
+       memcpy(_platform_descriptor->CapabilityData, capability_data, capability_data_length);
+
+       *platform_descriptor = _platform_descriptor;
+       return LIBUSB_SUCCESS;
+}
+
+/** \ingroup libusb_desc
+ * Free a platform descriptor obtained from
+ * libusb_get_platform_descriptor().
+ * It is safe to call this function with a NULL platform_descriptor parameter,
+ * in which case the function simply returns.
+ *
+ * \param platform_descriptor the Platform descriptor to free
+ */
+void API_EXPORTED libusb_free_platform_descriptor(
+       struct libusb_platform_descriptor *platform_descriptor)
+{
+       free(platform_descriptor);
+}
+
 /** \ingroup libusb_desc
  * Retrieve a string descriptor in C style ASCII.
  *
@@ -1086,7 +1150,7 @@ int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_ha
        uint8_t desc_index, unsigned char *data, int length)
 {
        union usbi_string_desc_buf str;
-       int r, si, di;
+       int r;
        uint16_t langid, wdata;
 
        /* Asking for the zero'th index is special - it returns a string
@@ -1122,18 +1186,214 @@ int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_ha
        else if ((str.desc.bLength & 1) || str.desc.bLength != r)
                usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor (read %d)", str.desc.bLength, r);
 
-       di = 0;
-       for (si = 2; si < str.desc.bLength; si += 2) {
-               if (di >= (length - 1))
-                       break;
+       /* Stop one byte before the end to leave room for null termination. */
+       int dest_max = length - 1;
+
+       /* The descriptor has this number of wide characters */
+       int src_max = (str.desc.bLength - 1 - 1) / 2;
 
-               wdata = libusb_le16_to_cpu(str.desc.wData[di]);
+       /* Neither read nor write more than the smallest buffer */
+       int idx_max = MIN(dest_max, src_max);
+
+       int idx;
+       for (idx = 0; idx < idx_max; ++idx) {
+               wdata = libusb_le16_to_cpu(str.desc.wData[idx]);
                if (wdata < 0x80)
-                       data[di++] = (unsigned char)wdata;
+                       data[idx] = (unsigned char)wdata;
                else
-                       data[di++] = '?'; /* non-ASCII */
+                       data[idx] = '?'; /* non-ASCII */
+       }
+
+       data[idx] = 0; /* null-terminate string */
+       return idx;
+}
+
+static int parse_iad_array(struct libusb_context *ctx,
+       struct libusb_interface_association_descriptor_array *iad_array,
+       const uint8_t *buffer, int size)
+{
+       uint8_t i;
+       struct usbi_descriptor_header header;
+       int consumed = 0;
+       const uint8_t *buf = buffer;
+       struct libusb_interface_association_descriptor *iad;
+
+       if (size < LIBUSB_DT_CONFIG_SIZE) {
+               usbi_err(ctx, "short config descriptor read %d/%d",
+                        size, LIBUSB_DT_CONFIG_SIZE);
+               return LIBUSB_ERROR_IO;
+       }
+
+       // First pass: Iterate through desc list, count number of IADs
+       iad_array->length = 0;
+       while (consumed < size) {
+               parse_descriptor(buf, "bb", &header);
+               if (header.bLength < 2) {
+                       usbi_err(ctx, "invalid descriptor bLength %d",
+                                header.bLength);
+                       return LIBUSB_ERROR_IO;
+               }
+               if (header.bDescriptorType == LIBUSB_DT_INTERFACE_ASSOCIATION)
+                       iad_array->length++;
+               buf += header.bLength;
+               consumed += header.bLength;
+       }
+
+       iad_array->iad = NULL;
+       if (iad_array->length > 0) {
+               iad = calloc(iad_array->length, sizeof(*iad));
+               if (!iad)
+                       return LIBUSB_ERROR_NO_MEM;
+
+               iad_array->iad = iad;
+
+               // Second pass: Iterate through desc list, fill IAD structures
+               consumed = 0;
+               i = 0;
+               while (consumed < size) {
+                  parse_descriptor(buffer, "bb", &header);
+                  if (header.bDescriptorType == LIBUSB_DT_INTERFACE_ASSOCIATION)
+                         parse_descriptor(buffer, "bbbbbbbb", &iad[i++]);
+                  buffer += header.bLength;
+                  consumed += header.bLength;
+               }
        }
 
-       data[di] = 0;
-       return di;
+       return LIBUSB_SUCCESS;
+}
+
+static int raw_desc_to_iad_array(struct libusb_context *ctx, const uint8_t *buf,
+               int size, struct libusb_interface_association_descriptor_array **iad_array)
+{
+       struct libusb_interface_association_descriptor_array *_iad_array
+               = calloc(1, sizeof(*_iad_array));
+       int r;
+
+       if (!_iad_array)
+               return LIBUSB_ERROR_NO_MEM;
+
+       r = parse_iad_array(ctx, _iad_array, buf, size);
+       if (r < 0) {
+               usbi_err(ctx, "parse_iad_array failed with error %d", r);
+               free(_iad_array);
+               return r;
+       }
+
+       *iad_array = _iad_array;
+       return LIBUSB_SUCCESS;
+}
+
+/** \ingroup libusb_desc
+ * Get an array of interface association descriptors (IAD) for a given
+ * configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param config_index the index of the configuration you wish to retrieve the
+ * IADs for.
+ * \param iad_array output location for the array of IADs. Only valid if 0 was
+ * returned. Must be freed with libusb_free_interface_association_descriptors()
+ * after use. It's possible that a given configuration contains no IADs. In this
+ * case the iad_array is still output, but will have 'length' field set to 0, and
+ * iad field set to NULL.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns another LIBUSB_ERROR code on error
+ * \see libusb_get_active_interface_association_descriptors()
+ */
+int API_EXPORTED libusb_get_interface_association_descriptors(libusb_device *dev,
+       uint8_t config_index, struct libusb_interface_association_descriptor_array **iad_array)
+{
+       union usbi_config_desc_buf _config;
+       uint16_t config_len;
+       uint8_t *buf;
+       int r;
+
+       if (!iad_array)
+               return LIBUSB_ERROR_INVALID_PARAM;
+
+       usbi_dbg(DEVICE_CTX(dev), "IADs for config index %u", config_index);
+       if (config_index >= dev->device_descriptor.bNumConfigurations)
+               return LIBUSB_ERROR_NOT_FOUND;
+
+       r = get_config_descriptor(dev, config_index, _config.buf, sizeof(_config.buf));
+       if (r < 0)
+               return r;
+
+       config_len = libusb_le16_to_cpu(_config.desc.wTotalLength);
+       buf = malloc(config_len);
+       if (!buf)
+               return LIBUSB_ERROR_NO_MEM;
+
+       r = get_config_descriptor(dev, config_index, buf, config_len);
+       if (r >= 0)
+               r = raw_desc_to_iad_array(DEVICE_CTX(dev), buf, r, iad_array);
+
+       free(buf);
+       return r;
+}
+
+/** \ingroup libusb_desc
+ * Get an array of interface association descriptors (IAD) for the currently
+ * active configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param iad_array output location for the array of IADs. Only valid if 0 was
+ * returned. Must be freed with libusb_free_interface_association_descriptors()
+ * after use. It's possible that a given configuration contains no IADs. In this
+ * case the iad_array is still output, but will have 'length' field set to 0, and
+ * iad field set to NULL.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state
+ * \returns another LIBUSB_ERROR code on error
+ * \see libusb_get_interface_association_descriptors
+ */
+int API_EXPORTED libusb_get_active_interface_association_descriptors(libusb_device *dev,
+       struct libusb_interface_association_descriptor_array **iad_array)
+{
+       union usbi_config_desc_buf _config;
+       uint16_t config_len;
+       uint8_t *buf;
+       int r;
+
+       if (!iad_array)
+               return LIBUSB_ERROR_INVALID_PARAM;
+
+       r = get_active_config_descriptor(dev, _config.buf, sizeof(_config.buf));
+       if (r < 0)
+               return r;
+
+       config_len = libusb_le16_to_cpu(_config.desc.wTotalLength);
+       buf = malloc(config_len);
+       if (!buf)
+               return LIBUSB_ERROR_NO_MEM;
+
+       r = get_active_config_descriptor(dev, buf, config_len);
+       if (r >= 0)
+               r = raw_desc_to_iad_array(DEVICE_CTX(dev), buf, r, iad_array);
+       free(buf);
+       return r;
+}
+
+/** \ingroup libusb_desc
+ * Free an array of interface association descriptors (IADs) obtained from
+ * libusb_get_interface_association_descriptors() or
+ * libusb_get_active_interface_association_descriptors().
+ * It is safe to call this function with a NULL iad_array parameter, in which
+ * case the function simply returns.
+ *
+ * \param iad_array the IAD array to free
+ */
+void API_EXPORTED libusb_free_interface_association_descriptors(
+       struct libusb_interface_association_descriptor_array *iad_array)
+{
+       if (!iad_array)
+               return;
+
+       if (iad_array->iad)
+               free((void*)iad_array->iad);
+       free(iad_array);
 }
index 6b743c70442e42080338bf92b8ee21630522ca35..3c64f69197b347244a0bedaa978dbe41cd0f41ca 100644 (file)
@@ -33,7 +33,7 @@
  *
  * \section hotplug_intro Introduction
  *
- * Version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, has added support
+ * Version 1.0.16, \ref LIBUSBX_API_VERSION >= 0x01000102, has added support
  * for hotplug events on <b>some</b> platforms (you should test if your platform
  * supports hotplug notification by calling \ref libusb_has_capability() with
  * parameter \ref LIBUSB_CAP_HAS_HOTPLUG).
@@ -117,7 +117,7 @@ int main (void) {
   libusb_hotplug_callback_handle callback_handle;
   int rc;
 
-  libusb_init(NULL);
+  libusb_init_context(NULL, NULL, 0);
 
   rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
                                         LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, 0x045a, 0x5005,
@@ -311,7 +311,7 @@ void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_
        for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) {
                if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) {
                        usbi_dbg(ctx, "freeing hotplug cb %p with handle %d",
-                               hotplug_cb, hotplug_cb->handle);
+                                (void *) hotplug_cb, hotplug_cb->handle);
                        list_del(&hotplug_cb->list);
                        free(hotplug_cb);
                }
@@ -377,7 +377,8 @@ int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx,
 
        usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
 
-       usbi_dbg(ctx, "new hotplug cb %p with handle %d", hotplug_cb, hotplug_cb->handle);
+       usbi_dbg(ctx, "new hotplug cb %p with handle %d",
+                (void *) hotplug_cb, hotplug_cb->handle);
 
        if ((flags & LIBUSB_HOTPLUG_ENUMERATE) && (events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)) {
                ssize_t i, len;
index 9e3146cf9fdb31223a3cd27f7998e797e7853bd6..ab84ba620f99f04bec674fb205849bd9aef0386b 100644 (file)
@@ -3,8 +3,8 @@
  * I/O functions for libusb
  * Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org>
  * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
- * Copyright © 2019 Nathan Hjelm <hjelmn@cs.umm.edu>
- * Copyright © 2019 Google LLC. All rights reserved.
+ * Copyright © 2019-2022 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * Copyright © 2019-2022 Google LLC. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -313,6 +313,10 @@ if (r == 0 && actual_length == sizeof(data)) {
  * be invoked, and the callback function should check the transfer status to
  * determine that it was cancelled.
  *
+ * On macOS and iOS it is not possible to cancel a single transfer. In this
+ * case cancelling one transfer on an endpoint will cause all transfers on
+ * that endpoint to be cancelled.
+ *
  * Freeing the transfer after it has been cancelled but before cancellation
  * has completed will result in undefined behaviour.
  *
@@ -1240,8 +1244,8 @@ void usbi_io_exit(struct libusb_context *ctx)
 
 static void calculate_timeout(struct usbi_transfer *itransfer)
 {
-       unsigned int timeout =
-               USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout;
+       struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       unsigned int timeout = transfer->timeout;
 
        if (!timeout) {
                TIMESPEC_CLEAR(&itransfer->timeout);
@@ -1285,30 +1289,25 @@ DEFAULT_VISIBILITY
 struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(
        int iso_packets)
 {
-       size_t priv_size;
-       size_t alloc_size;
-       unsigned char *ptr;
-       struct usbi_transfer *itransfer;
-       struct libusb_transfer *transfer;
-
        assert(iso_packets >= 0);
        if (iso_packets < 0)
                return NULL;
 
-       priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size);
-       alloc_size = priv_size
-               + sizeof(struct usbi_transfer)
-               + sizeof(struct libusb_transfer)
-               + (sizeof(struct libusb_iso_packet_descriptor) * (size_t)iso_packets);
-       ptr = calloc(1, alloc_size);
+       size_t priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size);
+       size_t usbi_transfer_size = PTR_ALIGN(sizeof(struct usbi_transfer));
+       size_t libusb_transfer_size = PTR_ALIGN(sizeof(struct libusb_transfer));
+       size_t iso_packets_size = sizeof(struct libusb_iso_packet_descriptor) * (size_t)iso_packets;
+       size_t alloc_size = priv_size + usbi_transfer_size + libusb_transfer_size + iso_packets_size;
+       unsigned char *ptr = calloc(1, alloc_size);
        if (!ptr)
                return NULL;
 
-       itransfer = (struct usbi_transfer *)(ptr + priv_size);
+       struct usbi_transfer *itransfer = (struct usbi_transfer *)(ptr + priv_size);
        itransfer->num_iso_packets = iso_packets;
        itransfer->priv = ptr;
        usbi_mutex_init(&itransfer->lock);
-       transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+
        return transfer;
 }
 
@@ -1331,31 +1330,26 @@ struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(
  */
 void API_EXPORTED libusb_free_transfer(struct libusb_transfer *transfer)
 {
-       struct usbi_transfer *itransfer;
-       size_t priv_size;
-       unsigned char *ptr;
-
        if (!transfer)
                return;
 
-       usbi_dbg(TRANSFER_CTX(transfer), "transfer %p", transfer);
+       usbi_dbg(TRANSFER_CTX(transfer), "transfer %p", (void *) transfer);
        if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER)
                free(transfer->buffer);
 
-       itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
+       struct usbi_transfer *itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
        usbi_mutex_destroy(&itransfer->lock);
        if (itransfer->dev)
                libusb_unref_device(itransfer->dev);
 
-       priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size);
-       ptr = (unsigned char *)itransfer - priv_size;
+       unsigned char *ptr = USBI_TRANSFER_TO_TRANSFER_PRIV(itransfer);
        assert(ptr == itransfer->priv);
        free(ptr);
 }
 
 /* iterates through the flying transfers, and rearms the timer based on the
  * next upcoming timeout.
- * must be called with flying_list locked.
+ * NB: flying_transfers_lock must be held when calling this.
  * returns 0 on success or a LIBUSB_ERROR code on failure.
  */
 #ifdef HAVE_OS_TIMER
@@ -1376,7 +1370,8 @@ static int arm_timer_for_next_timeout(struct libusb_context *ctx)
 
                /* act on first transfer that has not already been handled */
                if (!(itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))) {
-                       usbi_dbg(ctx, "next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
+                       struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+                       usbi_dbg(ctx, "next timeout originally %ums", transfer->timeout);
                        return usbi_arm_timer(&ctx->timer, cur_ts);
                }
        }
@@ -1394,7 +1389,8 @@ static inline int arm_timer_for_next_timeout(struct libusb_context *ctx)
 
 /* add a transfer to the (timeout-sorted) active transfers list.
  * This function will return non 0 if fails to update the timer,
- * in which case the transfer is *not* on the flying_transfers list. */
+ * in which case the transfer is *not* on the flying_transfers list.
+ * NB: flying_transfers_lock MUST be held when calling this. */
 static int add_to_flying_list(struct usbi_transfer *itransfer)
 {
        struct usbi_transfer *cur;
@@ -1438,8 +1434,9 @@ out:
        if (first && usbi_using_timer(ctx) && TIMESPEC_IS_SET(timeout)) {
                /* if this transfer has the lowest timeout of all active transfers,
                 * rearm the timer with this transfer's timeout */
+               struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
                usbi_dbg(ctx, "arm timer for timeout in %ums (first in line)",
-                       USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
+                       transfer->timeout);
                r = usbi_arm_timer(&ctx->timer, timeout);
        }
 #else
@@ -1455,20 +1452,19 @@ out:
 /* remove a transfer from the active transfers list.
  * This function will *always* remove the transfer from the
  * flying_transfers list. It will return a LIBUSB_ERROR code
- * if it fails to update the timer for the next timeout. */
+ * if it fails to update the timer for the next timeout.
+ * NB: flying_transfers_lock MUST be held when calling this. */
 static int remove_from_flying_list(struct usbi_transfer *itransfer)
 {
        struct libusb_context *ctx = ITRANSFER_CTX(itransfer);
        int rearm_timer;
        int r = 0;
 
-       usbi_mutex_lock(&ctx->flying_transfers_lock);
        rearm_timer = (TIMESPEC_IS_SET(&itransfer->timeout) &&
                list_first_entry(&ctx->flying_transfers, struct usbi_transfer, list) == itransfer);
        list_del(&itransfer->list);
        if (rearm_timer)
                r = arm_timer_for_next_timeout(ctx);
-       usbi_mutex_unlock(&ctx->flying_transfers_lock);
 
        return r;
 }
@@ -1479,11 +1475,11 @@ static int remove_from_flying_list(struct usbi_transfer *itransfer)
  *
  * \param transfer the transfer to submit
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_BUSY if the transfer has already been submitted.
- * \returns LIBUSB_ERROR_NOT_SUPPORTED if the transfer flags are not supported
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_BUSY if the transfer has already been submitted.
+ * \returns \ref LIBUSB_ERROR_NOT_SUPPORTED if the transfer flags are not supported
  * by the operating system.
- * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
  * the operating system and/or hardware can support (see \ref asynclimits)
  * \returns another LIBUSB_ERROR code on other failure
  */
@@ -1500,7 +1496,7 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer)
        itransfer->dev = libusb_ref_device(transfer->dev_handle->dev);
 
        ctx = HANDLE_CTX(transfer->dev_handle);
-       usbi_dbg(ctx, "transfer %p", transfer);
+       usbi_dbg(ctx, "transfer %p", (void *) transfer);
 
        /*
         * Important note on locking, this function takes / releases locks
@@ -1562,8 +1558,11 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer)
        }
        usbi_mutex_unlock(&itransfer->lock);
 
-       if (r != LIBUSB_SUCCESS)
+       if (r != LIBUSB_SUCCESS) {
+               usbi_mutex_lock(&ctx->flying_transfers_lock);
                remove_from_flying_list(itransfer);
+               usbi_mutex_unlock(&ctx->flying_transfers_lock);
+       }
 
        return r;
 }
@@ -1584,21 +1583,23 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer)
  *   \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED
  *   "LIBUSB_TRANSFER_CANCELLED" for each transfer that was cancelled.
 
- * - Calling this function also sends a \c ClearFeature(ENDPOINT_HALT) request
- *   for the transfer's endpoint. If the device does not handle this request
- *   correctly, the data toggle bits for the endpoint can be left out of sync
- *   between host and device, which can have unpredictable results when the
- *   next data is sent on the endpoint, including data being silently lost.
- *   A call to \ref libusb_clear_halt will not resolve this situation, since
- *   that function uses the same request. Therefore, if your program runs on
- *   Darwin and uses a device that does not correctly implement
- *   \c ClearFeature(ENDPOINT_HALT) requests, it may only be safe to cancel
- *   transfers when followed by a device reset using
+ * - When built for macOS versions prior to 10.5, this function sends a
+ *   \c ClearFeature(ENDPOINT_HALT) request for the transfer's endpoint.
+ *   (Prior to libusb 1.0.27, this request was sent on all Darwin systems.)
+ *   If the device does not handle this request correctly, the data toggle
+ *   bits for the endpoint can be left out of sync between host and device,
+ *   which can have unpredictable results when the next data is sent on
+ *   the endpoint, including data being silently lost. A call to
+ *   \ref libusb_clear_halt will not resolve this situation, since that
+ *   function uses the same request. Therefore, if your program runs on
+ *   macOS < 10.5 (or libusb < 1.0.27), and uses a device that does not
+ *   correctly implement \c ClearFeature(ENDPOINT_HALT) requests, it may
+ *   only be safe to cancel transfers when followed by a device reset using
  *   \ref libusb_reset_device.
  *
  * \param transfer the transfer to cancel
  * \returns 0 on success
- * \returns LIBUSB_ERROR_NOT_FOUND if the transfer is not in progress,
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the transfer is not in progress,
  * already complete, or already cancelled.
  * \returns a LIBUSB_ERROR code on failure
  */
@@ -1609,7 +1610,7 @@ int API_EXPORTED libusb_cancel_transfer(struct libusb_transfer *transfer)
        struct libusb_context *ctx = ITRANSFER_CTX(itransfer);
        int r;
 
-       usbi_dbg(ctx, "transfer %p", transfer );
+       usbi_dbg(ctx, "transfer %p", (void *) transfer );
        usbi_mutex_lock(&itransfer->lock);
        if (!(itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT)
                        || (itransfer->state_flags & USBI_TRANSFER_CANCELLING)) {
@@ -1689,7 +1690,9 @@ int usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
        uint8_t flags;
        int r;
 
+       usbi_mutex_lock(&ctx->flying_transfers_lock);
        r = remove_from_flying_list(itransfer);
+       usbi_mutex_unlock(&ctx->flying_transfers_lock);
        if (r < 0)
                usbi_err(ctx, "failed to set timer for next timeout");
 
@@ -1711,9 +1714,13 @@ int usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
        flags = transfer->flags;
        transfer->status = status;
        transfer->actual_length = itransfer->transferred;
-       usbi_dbg(ctx, "transfer %p has callback %p", transfer, transfer->callback);
-       if (transfer->callback)
+       usbi_dbg(ctx, "transfer %p has callback %p",
+                (void *) transfer, transfer->callback);
+       if (transfer->callback) {
+               libusb_lock_event_waiters (ctx);
                transfer->callback(transfer);
+               libusb_unlock_event_waiters(ctx);
+       }
        /* transfer might have been freed by the above call, do not use from
         * this point. */
        if (flags & LIBUSB_TRANSFER_FREE_TRANSFER)
@@ -2013,7 +2020,7 @@ void API_EXPORTED libusb_unlock_event_waiters(libusb_context *ctx)
  * indicates unlimited timeout.
  * \returns 0 after a transfer completes or another thread stops event handling
  * \returns 1 if the timeout expired
- * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
  * \ref libusb_mtasync
  */
 int API_EXPORTED libusb_wait_for_event(libusb_context *ctx, struct timeval *tv)
@@ -2037,6 +2044,7 @@ int API_EXPORTED libusb_wait_for_event(libusb_context *ctx, struct timeval *tv)
        return 0;
 }
 
+// NB: flying_transfers_lock must be held when calling this
 static void handle_timeout(struct usbi_transfer *itransfer)
 {
        struct libusb_transfer *transfer =
@@ -2052,6 +2060,7 @@ static void handle_timeout(struct usbi_transfer *itransfer)
                        "async cancel failed %d", r);
 }
 
+// NB: flying_transfers_lock must be held when calling this
 static void handle_timeouts_locked(struct libusb_context *ctx)
 {
        struct timespec systime;
@@ -2332,7 +2341,7 @@ static int get_next_timeout(libusb_context *ctx, struct timeval *tv,
  * timeval struct for non-blocking mode
  * \param completed pointer to completion integer to check, or NULL
  * \returns 0 on success
- * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
  * \returns another LIBUSB_ERROR code on other failure
  * \ref libusb_mtasync
  */
@@ -2474,7 +2483,7 @@ int API_EXPORTED libusb_handle_events_completed(libusb_context *ctx,
  * \param tv the maximum time to block waiting for events, or zero for
  * non-blocking mode
  * \returns 0 on success
- * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
  * \returns another LIBUSB_ERROR code on other failure
  * \ref libusb_mtasync
  */
@@ -2558,7 +2567,7 @@ int API_EXPORTED libusb_pollfds_handle_timeouts(libusb_context *ctx)
  * \param tv output location for a relative time against the current
  * clock in which libusb must be called into in order to process timeout events
  * \returns 0 if there are no pending timeouts, 1 if a timeout was returned,
- * or LIBUSB_ERROR_OTHER on failure
+ * or \ref LIBUSB_ERROR_OTHER on failure
  */
 int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx,
        struct timeval *tv)
@@ -2619,11 +2628,11 @@ int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx,
  * To remove notifiers, pass NULL values for the function pointers.
  *
  * Note that file descriptors may have been added even before you register
- * these notifiers (e.g. at libusb_init() time).
+ * these notifiers (e.g. at libusb_init_context() time).
  *
  * Additionally, note that the removal notifier may be called during
  * libusb_exit() (e.g. when it is closing file descriptors that were opened
- * and added to the poll set at libusb_init() time). If you don't want this,
+ * and added to the poll set at libusb_init_context() time). If you don't want this,
  * remove the notifiers immediately before calling libusb_exit().
  *
  * \param ctx the context to operate on, or NULL for the default context
@@ -2827,7 +2836,8 @@ void usbi_handle_disconnect(struct libusb_device_handle *dev_handle)
                to_cancel = NULL;
                usbi_mutex_lock(&ctx->flying_transfers_lock);
                for_each_transfer(ctx, cur) {
-                       if (USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur)->dev_handle == dev_handle) {
+                       struct libusb_transfer *cur_transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur);
+                       if (cur_transfer->dev_handle == dev_handle) {
                                usbi_mutex_lock(&cur->lock);
                                if (cur->state_flags & USBI_TRANSFER_IN_FLIGHT)
                                        to_cancel = cur;
@@ -2842,8 +2852,9 @@ void usbi_handle_disconnect(struct libusb_device_handle *dev_handle)
                if (!to_cancel)
                        break;
 
+               struct libusb_transfer *transfer_to_cancel = USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel);
                usbi_dbg(ctx, "cancelling transfer %p from disconnect",
-                        USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel));
+                        (void *) transfer_to_cancel);
 
                usbi_mutex_lock(&to_cancel->lock);
                usbi_backend.clear_transfer_priv(to_cancel);
index 2592ea779a87121da2d495b424169584811fda22..f4e9203c641a123f39219ad30964f8c4e6e0c71b 100644 (file)
@@ -3,9 +3,9 @@
  * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
  * Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org>
  * Copyright © 2012 Pete Batard <pete@akeo.ie>
- * Copyright © 2012-2018 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * Copyright © 2012-2023 Nathan Hjelm <hjelmn@cs.unm.edu>
  * Copyright © 2014-2020 Chris Dickens <christopher.a.dickens@gmail.com>
- * For more information, please visit: http://libusb.info
+ * For more information, please visit: https://libusb.info
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -50,9 +50,9 @@ typedef SSIZE_T ssize_t;
 #include <time.h>
 
 #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
-#define ZERO_SIZED_ARRAY               /* [] - valid C99 code */
+#define LIBUSB_FLEXIBLE_ARRAY          /* [] - valid C99 code */
 #else
-#define ZERO_SIZED_ARRAY       0       /* [0] - non-standard, but usually working code */
+#define LIBUSB_FLEXIBLE_ARRAY  0       /* [0] - non-standard, but usually working code */
 #endif /* __STDC_VERSION__ */
 
 /* 'interface' might be defined as a macro on Windows, so we need to
@@ -74,6 +74,8 @@ typedef SSIZE_T ssize_t;
 #define LIBUSB_DEPRECATED_FOR(f) __attribute__ ((deprecated ("Use " #f " instead")))
 #elif defined(__GNUC__) && (__GNUC__ >= 3)
 #define LIBUSB_DEPRECATED_FOR(f) __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+#define LIBUSB_DEPRECATED_FOR(f) __declspec(deprecated("Use " #f " instead"))
 #else
 #define LIBUSB_DEPRECATED_FOR(f)
 #endif /* __GNUC__ */
@@ -118,20 +120,25 @@ typedef SSIZE_T ssize_t;
  */
 #if defined(_WIN32) || defined(__CYGWIN__)
 #define LIBUSB_CALL WINAPI
+#define LIBUSB_CALLV WINAPIV
 #else
 #define LIBUSB_CALL
+#define LIBUSB_CALLV
 #endif /* _WIN32 || __CYGWIN__ */
 
 /** \def LIBUSB_API_VERSION
  * \ingroup libusb_misc
  * libusb's API version.
  *
- * Since version 1.0.13, to help with feature detection, libusb defines
+ * Since version 1.0.18, to help with feature detection, libusb defines
  * a LIBUSB_API_VERSION macro that gets increased every time there is a
  * significant change to the API, such as the introduction of a new call,
  * the definition of a new macro/enum member, or any other element that
  * libusb applications may want to detect at compilation time.
  *
+ * Between versions 1.0.13 and 1.0.17 (inclusive) the older spelling of
+ * LIBUSBX_API_VERSION was used.
+ *
  * The macro is typically used in an application as follows:
  * \code
  * #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01001234)
@@ -141,10 +148,34 @@ typedef SSIZE_T ssize_t;
  *
  * Internally, LIBUSB_API_VERSION is defined as follows:
  * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental)
+ *
+ * The incremental component has changed as follows:
+ * <ul>
+ * <li>libusbx version 1.0.13: LIBUSBX_API_VERSION = 0x01000100
+ * <li>libusbx version 1.0.14: LIBUSBX_API_VERSION = 0x010000FF
+ * <li>libusbx version 1.0.15: LIBUSBX_API_VERSION = 0x01000101
+ * <li>libusbx version 1.0.16: LIBUSBX_API_VERSION = 0x01000102
+ * <li>libusbx version 1.0.17: LIBUSBX_API_VERSION = 0x01000102
+ * <li>libusb version 1.0.18: LIBUSB_API_VERSION = 0x01000102
+ * <li>libusb version 1.0.19: LIBUSB_API_VERSION = 0x01000103
+ * <li>libusb version 1.0.20: LIBUSB_API_VERSION = 0x01000104
+ * <li>libusb version 1.0.21: LIBUSB_API_VERSION = 0x01000105
+ * <li>libusb version 1.0.22: LIBUSB_API_VERSION = 0x01000106
+ * <li>libusb version 1.0.23: LIBUSB_API_VERSION = 0x01000107
+ * <li>libusb version 1.0.24: LIBUSB_API_VERSION = 0x01000108
+ * <li>libusb version 1.0.25: LIBUSB_API_VERSION = 0x01000109
+ * <li>libusb version 1.0.26: LIBUSB_API_VERSION = 0x01000109
+ * <li>libusb version 1.0.27: LIBUSB_API_VERSION = 0x0100010A
+ * </ul>
  */
-#define LIBUSB_API_VERSION 0x01000109
+#define LIBUSB_API_VERSION 0x0100010A
 
-/* The following is kept for compatibility, but will be deprecated in the future */
+/** \def LIBUSBX_API_VERSION
+ * \ingroup libusb_misc
+ *
+ * This is the older spelling, kept for backwards compatibility of code
+ * needing to test for older library versions where the newer spelling
+ * did not exist. */
 #define LIBUSBX_API_VERSION LIBUSB_API_VERSION
 
 #if defined(__cplusplus)
@@ -265,6 +296,10 @@ enum libusb_descriptor_type {
        /** Endpoint descriptor. See libusb_endpoint_descriptor. */
        LIBUSB_DT_ENDPOINT = 0x05,
 
+       /** Interface Association Descriptor.
+       * See libusb_interface_association_descriptor */
+       LIBUSB_DT_INTERFACE_ASSOCIATION = 0x0b,
+
        /** BOS descriptor */
        LIBUSB_DT_BOS = 0x0f,
 
@@ -305,6 +340,7 @@ enum libusb_descriptor_type {
 #define LIBUSB_BT_USB_2_0_EXTENSION_SIZE       7
 #define LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE        10
 #define LIBUSB_BT_CONTAINER_ID_SIZE            20
+#define LIBUSB_BT_PLATFORM_DESCRIPTOR_MIN_SIZE         20
 
 /* We unwrap the BOS => define its max size */
 #define LIBUSB_DT_BOS_MAX_SIZE                         \
@@ -523,7 +559,10 @@ enum libusb_bos_type {
        LIBUSB_BT_SS_USB_DEVICE_CAPABILITY = 0x03,
 
        /** Container ID type */
-       LIBUSB_BT_CONTAINER_ID = 0x04
+       LIBUSB_BT_CONTAINER_ID = 0x04,
+
+       /** Platform descriptor */
+       LIBUSB_BT_PLATFORM_DESCRIPTOR = 0x05
 };
 
 /** \ingroup libusb_desc
@@ -628,6 +667,65 @@ struct libusb_endpoint_descriptor {
        int extra_length;
 };
 
+/** \ingroup libusb_desc
+ * A structure representing the standard USB interface association descriptor.
+ * This descriptor is documented in section 9.6.4 of the USB 3.0 specification.
+ * All multiple-byte fields are represented in host-endian format.
+ */
+struct libusb_interface_association_descriptor {
+       /** Size of this descriptor (in bytes) */
+       uint8_t  bLength;
+
+       /** Descriptor type. Will have value
+       * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE_ASSOCIATION
+       * LIBUSB_DT_INTERFACE_ASSOCIATION in this context. */
+       uint8_t  bDescriptorType;
+
+       /** Interface number of the first interface that is associated
+       * with this function */
+       uint8_t  bFirstInterface;
+
+       /** Number of contiguous interfaces that are associated with
+       * this function */
+       uint8_t  bInterfaceCount;
+
+       /** USB-IF class code for this function.
+       * A value of zero is not allowed in this descriptor.
+       * If this field is 0xff, the function class is vendor-specific.
+       * All other values are reserved for assignment by the USB-IF.
+       */
+       uint8_t  bFunctionClass;
+
+       /** USB-IF subclass code for this function.
+       * If this field is not set to 0xff, all values are reserved
+       * for assignment by the USB-IF
+       */
+       uint8_t  bFunctionSubClass;
+
+       /** USB-IF protocol code for this function.
+       * These codes are qualified by the values of the bFunctionClass
+       * and bFunctionSubClass fields.
+       */
+       uint8_t  bFunctionProtocol;
+
+       /** Index of string descriptor describing this function */
+       uint8_t  iFunction;
+};
+
+/** \ingroup libusb_desc
+ * Structure containing an array of 0 or more interface association
+ * descriptors
+ */
+struct libusb_interface_association_descriptor_array {
+       /** Array of interface association descriptors. The size of this array
+        * is determined by the length field.
+        */
+       const struct libusb_interface_association_descriptor *iad;
+
+       /** Number of interface association descriptors contained. Read-only. */
+       int length;
+};
+
 /** \ingroup libusb_desc
  * A structure representing the standard USB interface descriptor. This
  * descriptor is documented in section 9.6.5 of the USB 3.0 specification.
@@ -786,7 +884,7 @@ struct libusb_bos_dev_capability_descriptor {
        uint8_t  bDevCapabilityType;
 
        /** Device Capability data (bLength - 3 bytes) */
-       uint8_t  dev_capability_data[ZERO_SIZED_ARRAY];
+       uint8_t  dev_capability_data[LIBUSB_FLEXIBLE_ARRAY];
 };
 
 /** \ingroup libusb_desc
@@ -811,7 +909,7 @@ struct libusb_bos_descriptor {
        uint8_t  bNumDeviceCaps;
 
        /** bNumDeviceCap Device Capability Descriptors */
-       struct libusb_bos_dev_capability_descriptor *dev_capability[ZERO_SIZED_ARRAY];
+       struct libusb_bos_dev_capability_descriptor *dev_capability[LIBUSB_FLEXIBLE_ARRAY];
 };
 
 /** \ingroup libusb_desc
@@ -908,6 +1006,34 @@ struct libusb_container_id_descriptor {
        uint8_t  ContainerID[16];
 };
 
+/** \ingroup libusb_desc
+ * A structure representing a Platform descriptor.
+ * This descriptor is documented in section 9.6.2.4 of the USB 3.2 specification.
+ */
+struct libusb_platform_descriptor {
+       /** Size of this descriptor (in bytes) */
+       uint8_t  bLength;
+
+       /** Descriptor type. Will have value
+        * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY
+        * LIBUSB_DT_DEVICE_CAPABILITY in this context. */
+       uint8_t  bDescriptorType;
+
+       /** Capability type. Will have value
+        * \ref libusb_capability_type::LIBUSB_BT_PLATFORM_DESCRIPTOR
+        * LIBUSB_BT_CONTAINER_ID in this context. */
+       uint8_t  bDevCapabilityType;
+
+       /** Reserved field */
+       uint8_t  bReserved;
+
+       /** 128 bit UUID */
+       uint8_t  PlatformCapabilityUUID[16];
+
+       /** Capability data (bLength - 20) */
+       uint8_t  CapabilityData[LIBUSB_FLEXIBLE_ARRAY];
+};
+
 /** \ingroup libusb_asyncio
  * Setup packet for control transfers. */
 #if defined(_MSC_VER) || defined(__WATCOMC__)
@@ -982,7 +1108,7 @@ struct libusb_version {
  * libusb_exit() will not destroy resources that the other user is still
  * using.
  *
- * Sessions are created by libusb_init() and destroyed through libusb_exit().
+ * Sessions are created by libusb_init_context() and destroyed through libusb_exit().
  * If your application is guaranteed to only ever include a single libusb
  * user (i.e. you), you do not have to worry about contexts: pass NULL in
  * every function call where a context is required, and the default context
@@ -1187,7 +1313,8 @@ enum libusb_transfer_flags {
         *
         * This flag is currently only supported on Linux.
         * On other systems, libusb_submit_transfer() will return
-        * LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag is set.
+        * \ref LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this
+        * flag is set.
         *
         * Available since libusb-1.0.9.
         */
@@ -1284,7 +1411,7 @@ struct libusb_transfer {
        int num_iso_packets;
 
        /** Isochronous packet descriptors, for isochronous transfers only. */
-       struct libusb_iso_packet_descriptor iso_packet_desc[ZERO_SIZED_ARRAY];
+       struct libusb_iso_packet_descriptor iso_packet_desc[LIBUSB_FLEXIBLE_ARRAY];
 };
 
 /** \ingroup libusb_misc
@@ -1345,6 +1472,79 @@ enum libusb_log_cb_mode {
        LIBUSB_LOG_CB_CONTEXT = (1 << 1)
 };
 
+/** \ingroup libusb_lib
+ * Available option values for libusb_set_option() and libusb_init_context().
+ */
+enum libusb_option {
+       /** Set the log message verbosity.
+        *
+        * This option must be provided an argument of type \ref libusb_log_level.
+        * The default level is LIBUSB_LOG_LEVEL_NONE, which means no messages are ever
+        * printed. If you choose to increase the message verbosity level, ensure
+        * that your application does not close the stderr file descriptor.
+        *
+        * You are advised to use level LIBUSB_LOG_LEVEL_WARNING. libusb is conservative
+        * with its message logging and most of the time, will only log messages that
+        * explain error conditions and other oddities. This will help you debug
+        * your software.
+        *
+        * If the LIBUSB_DEBUG environment variable was set when libusb was
+        * initialized, this option does nothing: the message verbosity is fixed
+        * to the value in the environment variable.
+        *
+        * If libusb was compiled without any message logging, this option does
+        * nothing: you'll never get any messages.
+        *
+        * If libusb was compiled with verbose debug message logging, this option
+        * does nothing: you'll always get messages from all levels.
+        */
+       LIBUSB_OPTION_LOG_LEVEL = 0,
+
+       /** Use the UsbDk backend for a specific context, if available.
+        *
+        * This option should be set at initialization with libusb_init_context()
+        * otherwise unspecified behavior may occur.
+        *
+        * Only valid on Windows. Ignored on all other platforms.
+        */
+       LIBUSB_OPTION_USE_USBDK = 1,
+
+       /** Do not scan for devices
+        *
+        * With this option set, libusb will skip scanning devices in
+        * libusb_init_context().
+        *
+        * Hotplug functionality will also be deactivated.
+        *
+        * The option is useful in combination with libusb_wrap_sys_device(),
+        * which can access a device directly without prior device scanning.
+        *
+        * This is typically needed on Android, where access to USB devices
+        * is limited.
+        *
+        * This option should only be used with libusb_init_context()
+        * otherwise unspecified behavior may occur.
+        *
+        * Only valid on Linux. Ignored on all other platforms.
+        */
+       LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2,
+
+#define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY
+
+       /** Set the context log callback function.
+        *
+        * Set the log callback function either on a context or globally. This
+        * option must be provided an argument of type \ref libusb_log_cb.
+        * Using this option with a NULL context is equivalent to calling
+        * libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_GLOBAL.
+        * Using it with a non-NULL context is equivalent to calling
+        * libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_CONTEXT.
+        */
+       LIBUSB_OPTION_LOG_CB = 3,
+
+       LIBUSB_OPTION_MAX = 4
+};
+
 /** \ingroup libusb_lib
  * Callback function for handling log messages.
  * \param ctx the context which is related to the log message, or NULL if it
@@ -1359,10 +1559,25 @@ enum libusb_log_cb_mode {
 typedef void (LIBUSB_CALL *libusb_log_cb)(libusb_context *ctx,
        enum libusb_log_level level, const char *str);
 
+/** \ingroup libusb_lib
+ * Structure used for setting options through \ref libusb_init_context.
+ *
+ */
+struct libusb_init_option {
+  /** Which option to set */
+  enum libusb_option option;
+  /** An integer value used by the option (if applicable). */
+  union {
+    int ival;
+    libusb_log_cb log_cbval;
+  } value;
+};
+
 int LIBUSB_CALL libusb_init(libusb_context **ctx);
+int LIBUSB_CALL libusb_init_context(libusb_context **ctx, const struct libusb_init_option options[], int num_options);
 void LIBUSB_CALL libusb_exit(libusb_context *ctx);
-LIBUSB_DEPRECATED_FOR(libusb_set_option)
 void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level);
+/* may be deprecated in the future in favor of lubusb_init_context()+libusb_set_option() */
 void LIBUSB_CALL libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb, int mode);
 const struct libusb_version * LIBUSB_CALL libusb_get_version(void);
 int LIBUSB_CALL libusb_has_capability(uint32_t capability);
@@ -1415,6 +1630,11 @@ int LIBUSB_CALL libusb_get_container_id_descriptor(libusb_context *ctx,
        struct libusb_container_id_descriptor **container_id);
 void LIBUSB_CALL libusb_free_container_id_descriptor(
        struct libusb_container_id_descriptor *container_id);
+int LIBUSB_CALL libusb_get_platform_descriptor(libusb_context *ctx,
+       struct libusb_bos_dev_capability_descriptor *dev_cap,
+       struct libusb_platform_descriptor **platform_descriptor);
+void LIBUSB_CALL libusb_free_platform_descriptor(
+       struct libusb_platform_descriptor *platform_descriptor);
 uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev);
 uint8_t LIBUSB_CALL libusb_get_port_number(libusb_device *dev);
 int LIBUSB_CALL libusb_get_port_numbers(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len);
@@ -1427,6 +1647,15 @@ int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev,
        unsigned char endpoint);
 int LIBUSB_CALL libusb_get_max_iso_packet_size(libusb_device *dev,
        unsigned char endpoint);
+int LIBUSB_CALL libusb_get_max_alt_packet_size(libusb_device *dev,
+       int interface_number, int alternate_setting, unsigned char endpoint);
+
+int LIBUSB_CALL libusb_get_interface_association_descriptors(libusb_device *dev,
+       uint8_t config_index, struct libusb_interface_association_descriptor_array **iad_array);
+int LIBUSB_CALL libusb_get_active_interface_association_descriptors(libusb_device *dev,
+       struct libusb_interface_association_descriptor_array **iad_array);
+void LIBUSB_CALL libusb_free_interface_association_descriptors(
+       struct libusb_interface_association_descriptor_array *iad_array);
 
 int LIBUSB_CALL libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev, libusb_device_handle **dev_handle);
 int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **dev_handle);
@@ -2036,7 +2265,7 @@ typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx,
  * \param[in] cb_fn the function to be invoked on a matching event/device
  * \param[in] user_data user data to pass to the callback function
  * \param[out] callback_handle pointer to store the handle of the allocated callback (can be NULL)
- * \returns LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure
+ * \returns \ref LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure
  */
 int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx,
        int events, int flags,
@@ -2069,67 +2298,7 @@ void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx,
 void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx,
        libusb_hotplug_callback_handle callback_handle);
 
-/** \ingroup libusb_lib
- * Available option values for libusb_set_option().
- */
-enum libusb_option {
-       /** Set the log message verbosity.
-        *
-        * The default level is LIBUSB_LOG_LEVEL_NONE, which means no messages are ever
-        * printed. If you choose to increase the message verbosity level, ensure
-        * that your application does not close the stderr file descriptor.
-        *
-        * You are advised to use level LIBUSB_LOG_LEVEL_WARNING. libusb is conservative
-        * with its message logging and most of the time, will only log messages that
-        * explain error conditions and other oddities. This will help you debug
-        * your software.
-        *
-        * If the LIBUSB_DEBUG environment variable was set when libusb was
-        * initialized, this function does nothing: the message verbosity is fixed
-        * to the value in the environment variable.
-        *
-        * If libusb was compiled without any message logging, this function does
-        * nothing: you'll never get any messages.
-        *
-        * If libusb was compiled with verbose debug message logging, this function
-        * does nothing: you'll always get messages from all levels.
-        */
-       LIBUSB_OPTION_LOG_LEVEL = 0,
-
-       /** Use the UsbDk backend for a specific context, if available.
-        *
-        * This option should be set immediately after calling libusb_init(), otherwise
-        * unspecified behavior may occur.
-        *
-        * Only valid on Windows.
-        */
-       LIBUSB_OPTION_USE_USBDK = 1,
-
-       /** Do not scan for devices
-        *
-        * With this option set, libusb will skip scanning devices in
-        * libusb_init(). Must be set before calling libusb_init().
-        *
-        * Hotplug functionality will also be deactivated.
-        *
-        * The option is useful in combination with libusb_wrap_sys_device(),
-        * which can access a device directly without prior device scanning.
-        *
-        * This is typically needed on Android, where access to USB devices
-        * is limited.
-        *
-        * For LIBUSB_API_VERSION 0x01000108 it was called LIBUSB_OPTION_WEAK_AUTHORITY
-        *
-        * Only valid on Linux.
-        */
-       LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2,
-
-#define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY
-
-       LIBUSB_OPTION_MAX = 3
-};
-
-int LIBUSB_CALL libusb_set_option(libusb_context *ctx, enum libusb_option option, ...);
+int LIBUSB_CALLV libusb_set_option(libusb_context *ctx, enum libusb_option option, ...);
 
 #ifdef _MSC_VER
 #pragma warning(pop)
index b1fc88c991338dc9c60726dcde9bb19aa8404525..3b0c6105c8de9775cbc9502457b66676c498ef1b 100644 (file)
@@ -73,7 +73,7 @@
 #endif
 
 /* The following is used to silence warnings for unused variables */
-#if defined(UNREFERENCED_PARAMETER)
+#if defined(UNREFERENCED_PARAMETER) && !defined(__GNUC__)
 #define UNUSED(var)    UNREFERENCED_PARAMETER(var)
 #else
 #define UNUSED(var)    do { (void)(var); } while(0)
@@ -128,6 +128,7 @@ typedef atomic_long usbi_atomic_t;
  *   return_type LIBUSB_CALL function_name(params);
  */
 #define API_EXPORTED LIBUSB_CALL DEFAULT_VISIBILITY
+#define API_EXPORTEDV LIBUSB_CALLV DEFAULT_VISIBILITY
 
 #ifdef __cplusplus
 extern "C" {
@@ -321,10 +322,10 @@ void usbi_log(struct libusb_context *ctx, enum libusb_log_level level,
 
 #else /* ENABLE_LOGGING */
 
-#define usbi_err(ctx, ...)     UNUSED(ctx)
-#define usbi_warn(ctx, ...)    UNUSED(ctx)
-#define usbi_info(ctx, ...)    UNUSED(ctx)
-#define usbi_dbg(ctx, ...)     do {} while (0)
+#define usbi_err(ctx, ...)     do { (void)(ctx); } while(0)
+#define usbi_warn(ctx, ...)    do { (void)(ctx); } while(0)
+#define usbi_info(ctx, ...)    do { (void)(ctx); } while(0)
+#define usbi_dbg(ctx, ...)     do { (void)(ctx); } while(0)
 
 #endif /* ENABLE_LOGGING */
 
@@ -379,7 +380,7 @@ struct libusb_context {
        struct list_head flying_transfers;
        /* Note paths taking both this and usbi_transfer->lock must always
         * take this lock first */
-       usbi_mutex_t flying_transfers_lock;
+       usbi_mutex_t flying_transfers_lock; /* for flying_transfers and timeout_flags */
 
 #if !defined(PLATFORM_WINDOWS)
        /* user callbacks for pollfd changes */
@@ -533,7 +534,7 @@ static inline void usbi_localize_device_descriptor(struct libusb_device_descript
        desc->bcdDevice = libusb_le16_to_cpu(desc->bcdDevice);
 }
 
-#ifdef HAVE_CLOCK_GETTIME
+#if defined(HAVE_CLOCK_GETTIME) && !defined(__APPLE__)
 static inline void usbi_get_monotonic_time(struct timespec *tp)
 {
        ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, tp), 0);
@@ -562,8 +563,11 @@ void usbi_get_real_time(struct timespec *tp);
  * 2. struct usbi_transfer
  * 3. struct libusb_transfer (which includes iso packets) [variable size]
  *
- * from a libusb_transfer, you can get the usbi_transfer by rewinding the
- * appropriate number of bytes.
+ * You can convert between them with the macros:
+ *  TRANSFER_PRIV_TO_USBI_TRANSFER
+ *  USBI_TRANSFER_TO_TRANSFER_PRIV
+ *  USBI_TRANSFER_TO_LIBUSB_TRANSFER
+ *  LIBUSB_TRANSFER_TO_USBI_TRANSFER
  */
 
 struct usbi_transfer {
@@ -574,7 +578,7 @@ struct usbi_transfer {
        int transferred;
        uint32_t stream_id;
        uint32_t state_flags;   /* Protected by usbi_transfer->lock */
-       uint32_t timeout_flags; /* Protected by the flying_stransfers_lock */
+       uint32_t timeout_flags; /* Protected by the flying_transfers_lock */
 
        /* The device reference is held until destruction for logging
         * even after dev_handle is set to NULL.  */
@@ -616,10 +620,21 @@ enum usbi_transfer_timeout_flags {
        USBI_TRANSFER_TIMED_OUT = 1U << 2,
 };
 
+#define TRANSFER_PRIV_TO_USBI_TRANSFER(transfer_priv) \
+       ((struct usbi_transfer *)                       \
+        ((unsigned char *)(transfer_priv)      \
+         + PTR_ALIGN(sizeof(*transfer_priv))))
+
+#define USBI_TRANSFER_TO_TRANSFER_PRIV(itransfer) \
+       ((unsigned char *)                      \
+        ((unsigned char *)(itransfer)  \
+         - PTR_ALIGN(usbi_backend.transfer_priv_size)))
+
 #define USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)    \
        ((struct libusb_transfer *)                     \
         ((unsigned char *)(itransfer)                  \
          + PTR_ALIGN(sizeof(struct usbi_transfer))))
+
 #define LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)     \
        ((struct usbi_transfer *)                       \
         ((unsigned char *)(transfer)                   \
@@ -678,7 +693,7 @@ struct usbi_interface_descriptor {
 struct usbi_string_descriptor {
        uint8_t  bLength;
        uint8_t  bDescriptorType;
-       uint16_t wData[ZERO_SIZED_ARRAY];
+       uint16_t wData[LIBUSB_FLEXIBLE_ARRAY];
 } LIBUSB_PACKED;
 
 struct usbi_bos_descriptor {
@@ -813,6 +828,7 @@ struct usbi_option {
   int is_set;
   union {
     int ival;
+    libusb_log_cb log_cbval;
   } arg;
 };
 
@@ -891,7 +907,7 @@ static inline void *usbi_get_transfer_priv(struct usbi_transfer *itransfer)
 struct discovered_devs {
        size_t len;
        size_t capacity;
-       struct libusb_device *devices[ZERO_SIZED_ARRAY];
+       struct libusb_device *devices[LIBUSB_FLEXIBLE_ARRAY];
 };
 
 struct discovered_devs *discovered_devs_append(
@@ -1180,6 +1196,8 @@ struct usbi_os_backend {
         * claiming, no other drivers/applications can use the interface because
         * we now "own" it.
         *
+        * This function gets called with dev_handle->lock locked!
+        *
         * Return:
         * - 0 on success
         * - LIBUSB_ERROR_NOT_FOUND if the interface does not exist
@@ -1199,6 +1217,8 @@ struct usbi_os_backend {
         * You will only ever be asked to release an interface which was
         * successfully claimed earlier.
         *
+        * This function gets called with dev_handle->lock locked!
+        *
         * Return:
         * - 0 on success
         * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it
@@ -1335,7 +1355,7 @@ struct usbi_os_backend {
         *
         * This function must not block.
         *
-        * This function gets called with the flying_transfers_lock locked!
+        * This function gets called with itransfer->lock locked!
         *
         * Return:
         * - 0 on success
@@ -1349,6 +1369,8 @@ struct usbi_os_backend {
         * This function must not block. The transfer cancellation must complete
         * later, resulting in a call to usbi_handle_transfer_cancellation()
         * from the context of handle_events.
+        *
+        * This function gets called with itransfer->lock locked!
         */
        int (*cancel_transfer)(struct usbi_transfer *itransfer);
 
index 388dbca6077b9aed782e5f0d92e34d2a35596a50..c0963e09b3f7c8f74edcfc4680a0cb52f205f643 100644 (file)
@@ -1,8 +1,8 @@
 /* -*- Mode: C; indent-tabs-mode:nil -*- */
 /*
  * darwin backend for libusb 1.0
- * Copyright © 2008-2021 Nathan Hjelm <hjelmn@cs.unm.edu>
- * Copyright © 2019-2021 Google LLC. All rights reserved.
+ * Copyright © 2008-2023 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * Copyright © 2019-2023 Google LLC. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 #include <fcntl.h>
 #include <sys/sysctl.h>
 
-#include <mach/clock.h>
-#include <mach/clock_types.h>
-#include <mach/mach_host.h>
-#include <mach/mach_port.h>
+#include <mach/mach_time.h>
 
 /* Suppress warnings about the use of the deprecated objc_registerThreadWithCollector
  * function. Its use is also conditionalized to only older deployment targets. */
@@ -58,28 +55,34 @@ static int init_count = 0;
 static const mach_port_t darwin_default_master_port = 0;
 
 /* async event thread */
+/* if both this mutex and darwin_cached_devices_mutex are to be acquired then
+   darwin_cached_devices_mutex must be acquired first. */
 static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t  libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER;
 
-#if !defined(HAVE_CLOCK_GETTIME)
-static clock_serv_t clock_realtime;
-static clock_serv_t clock_monotonic;
-#endif
-
 #define LIBUSB_DARWIN_STARTUP_FAILURE ((CFRunLoopRef) -1)
 
 static CFRunLoopRef libusb_darwin_acfl = NULL; /* event cf loop */
 static CFRunLoopSourceRef libusb_darwin_acfls = NULL; /* shutdown signal for event cf loop */
 
-static usbi_mutex_t darwin_cached_devices_lock = PTHREAD_MUTEX_INITIALIZER;
+static usbi_mutex_t darwin_cached_devices_mutex = PTHREAD_MUTEX_INITIALIZER;
 static struct list_head darwin_cached_devices;
 static const char *darwin_device_class = "IOUSBDevice";
 
+uint32_t libusb_testonly_fake_running_version __attribute__ ((visibility ("hidden")));
+int libusb_testonly_using_running_interface_version __attribute__ ((visibility ("hidden")));
+int libusb_testonly_using_running_device_version __attribute__ ((visibility ("hidden")));
+bool libusb_testonly_clear_running_version_cache __attribute__ ((visibility ("hidden")));
+
 #define DARWIN_CACHED_DEVICE(a) (((struct darwin_device_priv *)usbi_get_device_priv((a)))->dev)
 
 /* async event thread */
 static pthread_t libusb_darwin_at;
 
+/* protected by libusb_darwin_at_mutex */
+static bool libusb_darwin_at_started;
+
+static void darwin_exit(struct libusb_context *ctx);
 static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len);
 static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface);
 static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface);
@@ -96,6 +99,186 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct
 static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out,
                                                   UInt64 *old_session_id);
 
+struct darwin_iokit_interface {
+  uint32_t min_os_version;
+  uint32_t version;
+  CFUUIDRef interface_id;
+};
+
+static const struct darwin_iokit_interface *get_interface_interface(void) {
+  const struct darwin_iokit_interface interfaces[] = {
+#if defined(kIOUSBInterfaceInterfaceID800)
+    {
+      .min_os_version = 101200,
+      .version = 800,
+      .interface_id = kIOUSBInterfaceInterfaceID800,
+    },
+#endif
+#if defined(kIOUSBInterfaceInterfaceID700)
+    {
+      .min_os_version = 101000,
+      .version = 700,
+      .interface_id = kIOUSBInterfaceInterfaceID700,
+    },
+#endif
+#if defined(kIOUSBInterfaceInterfaceID650)
+    {
+      .min_os_version = 100900,
+      .version = 650,
+      .interface_id = kIOUSBInterfaceInterfaceID650
+    },
+#endif
+#if defined(kIOUSBInterfaceInterfaceID550)
+    {
+      .min_os_version = 100803,
+      .version = 550,
+      .interface_id = kIOUSBInterfaceInterfaceID550,
+    },
+#endif
+#if defined(kIOUSBInterfaceInterfaceID245)
+    {
+      .min_os_version = 100407,
+      .version = 245,
+      .interface_id = kIOUSBInterfaceInterfaceID245,
+    },
+#endif
+    {
+      .min_os_version = 100000,
+      .version = 220,
+      .interface_id = kIOUSBInterfaceInterfaceID220,
+    },
+    {
+      .version = 0,
+    },
+  };
+  static struct darwin_iokit_interface cached_interface = {.version = 0};
+  if (libusb_testonly_clear_running_version_cache) {
+    memset (&cached_interface, 0, sizeof (cached_interface));
+  }
+  if (0 == cached_interface.version) {
+    uint32_t os_version = get_running_version();
+    for (int i = 0 ; interfaces[i].version > 0 ; ++i) {
+      if (os_version >= interfaces[i].min_os_version && cached_interface.min_os_version < interfaces[i].min_os_version) {
+        cached_interface = interfaces[i];
+      }
+    }
+
+    libusb_testonly_using_running_interface_version = cached_interface.version;
+  }
+
+  return &cached_interface;
+}
+
+static CFUUIDRef get_interface_interface_id(void) {
+  return get_interface_interface()->interface_id;
+}
+
+static int get_interface_interface_version(void) {
+  return get_interface_interface()->version;
+}
+
+static const struct darwin_iokit_interface *get_device_interface(void) {
+  struct darwin_iokit_interface interfaces[] = {
+#if defined(kIOUSBDeviceInterfaceID650)
+    {
+      .min_os_version = 100900,
+      .version = 650,
+      .interface_id = kIOUSBDeviceInterfaceID650,
+    },
+#endif
+#if defined(kIOUSBDeviceInterfaceID500)
+    {
+      .min_os_version = 100703,
+      .version = 500,
+      .interface_id = kIOUSBDeviceInterfaceID500,
+    },
+#endif
+#if defined(kIOUSBDeviceInterfaceID320)
+    {
+      .min_os_version = 100504,
+      .version = 320,
+      .interface_id = kIOUSBDeviceInterfaceID320,
+    },
+#endif
+#if defined(kIOUSBDeviceInterfaceID300)
+    {
+      .min_os_version = 100500,
+      .version = 300,
+      .interface_id = kIOUSBDeviceInterfaceID300,
+    },
+#endif
+#if defined(kIOUSBDeviceInterfaceID245)
+    {
+      .min_os_version = 100407,
+      .version = 245,
+      .interface_id = kIOUSBDeviceInterfaceID245,
+    },
+#endif
+    {
+      .min_os_version = 100000,
+      .version = 197,
+      .interface_id = kIOUSBDeviceInterfaceID197,
+    },
+    {
+      .version = 0,
+    },
+  };
+  static struct darwin_iokit_interface cached_interface = {.version = 0};
+  if (libusb_testonly_clear_running_version_cache) {
+    memset (&cached_interface, 0, sizeof (cached_interface));
+  }
+  if (0 == cached_interface.version) {
+    uint32_t os_version = get_running_version();
+    for (int i = 0 ; interfaces[i].version > 0 ; ++i) {
+      if (os_version >= interfaces[i].min_os_version && cached_interface.min_os_version < interfaces[i].min_os_version) {
+        cached_interface = interfaces[i];
+      }
+    }
+    libusb_testonly_using_running_device_version = cached_interface.version;
+  }
+
+  return &cached_interface;
+}
+
+static CFUUIDRef get_device_interface_id(void) {
+  return get_device_interface()->interface_id;
+}
+
+static int get_device_interface_version(void) {
+  return get_device_interface()->version;
+}
+
+struct darwin_pipe_properties {
+  uint8_t number;
+  uint8_t direction;
+  uint8_t transfer_type;
+  uint16_t max_packet_size;
+  uint8_t interval;
+};
+typedef struct darwin_pipe_properties darwin_pipe_properties_t;
+
+static IOReturn darwin_get_pipe_properties(struct darwin_interface *cInterface, uint8_t pipe, darwin_pipe_properties_t *out) {
+  IOReturn kresult;
+
+#if (MAX_INTERFACE_VERSION >= 550)
+  if (get_interface_interface_version() >= 550) {
+    IOUSBEndpointProperties pipe_properties = {.bVersion = kUSBEndpointPropertiesVersion3};
+    kresult = (*IOINTERFACE_V(cInterface, 550))->GetPipePropertiesV3 (IOINTERFACE(cInterface), pipe, &pipe_properties);
+    if (kIOReturnSuccess == kresult) {
+      out->number = pipe_properties.bEndpointNumber;
+      out->direction = pipe_properties.bDirection;
+      out->transfer_type = pipe_properties.bTransferType;
+      out->max_packet_size = pipe_properties.wMaxPacketSize;
+      out->interval = pipe_properties.bInterval;
+    }
+    return kresult;
+  }
+#endif
+  return (*IOINTERFACE(cInterface))->GetPipeProperties(IOINTERFACE(cInterface), pipe, &out->direction,
+                                                               &out->number, &out->transfer_type, &out->max_packet_size,
+                                                               &out->interval);
+}
+
 #if defined(ENABLE_LOGGING)
 static const char *darwin_error_str (IOReturn result) {
   static char string_buffer[50];
@@ -172,7 +355,72 @@ static enum libusb_error darwin_to_libusb (IOReturn result) {
   }
 }
 
-/* this function must be called with the darwin_cached_devices_lock held */
+uint32_t get_running_version(void) {
+  if (libusb_testonly_fake_running_version > 0) {
+    return libusb_testonly_fake_running_version;
+  }
+
+  int ret;
+#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1
+  char os_version_string[64] = {'\0'};;
+  size_t os_version_string_len = sizeof(os_version_string) - 1;
+
+  /* newer versions of macOS provide a sysctl for the OS version but this is not useful for iOS without
+   * code detecting this is iOS and a mapping from iOS -> macOS version. it is still useful to have since
+   * it provides the exact macOS version instead of the approximate version (as below). */
+  ret = sysctlbyname("kern.osproductversion", os_version_string, &os_version_string_len, NULL, 0);
+  if (ret == 0) {
+    int major = 10, minor = 0, patch = 0;
+    ret = sscanf(os_version_string, "%i.%i.%i", &major, &minor, &patch);
+    if (ret < 2) {
+      usbi_err (NULL, "could not determine the running OS version, assuming 10.0, kern.osproductversion=%s", os_version_string);
+      return 100000;
+    }
+    return (major * 10000) + (minor * 100) + patch;
+  }
+#endif
+
+  char os_release_string[64] = {'\0'};
+  size_t os_release_string_len = sizeof(os_release_string) - 1;
+  /* if the version can not be detected libusb assumes 10.0 so ignore any error here */
+  ret = sysctlbyname("kern.osrelease", os_release_string, &os_release_string_len, NULL, 0);
+  if (ret != 0) {
+    usbi_err (NULL, "could not read kern.osrelease, errno=", errno);
+    return 100000;
+  }
+
+  int darwin_major = 1, darwin_minor = 0;
+  ret = sscanf(os_release_string, "%i.%i", &darwin_major, &darwin_minor);
+  if (ret < 1) {
+    usbi_err (NULL, "could not determine the running Darwin version, assuming 1.3 (OS X 10.0), kern.osrelease=%s", os_release_string);
+    return 100000;
+  }
+
+  int major = 10, minor = 0, patch = 0;
+
+  if (1 == darwin_major && darwin_minor < 4) {
+    /* 10.0.x */
+  } else if (darwin_major < 6) {
+    /* assume 10.1 for anything in this range */
+    minor = 1;
+  } else if (darwin_major < 20) {
+    /* from macOS 10.2 through 10.15 the minor version can be calculated from the darwin_major by subtracting 4 and
+     * the patch level almost always matches darwin_minor. when the darwin_minor does not match the OS X patch level
+     * it is usually because Apple did not change it in a particular point release. when darwin_minor is changed it
+     * always matches the OS X/macOS patch level. */
+    minor = darwin_major - 4;
+    patch = darwin_minor;
+  } else {
+    /* unlikely to be used as kern.osproductversion is available from 10.10 on */
+    major = darwin_major - 9;
+    minor = darwin_minor;
+    /* ignore the patch level in this range */
+  }
+
+  return (major * 10000) + (minor * 100) + patch;
+}
+
+/* this function must be called with the darwin_cached_devices_mutex held */
 static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) {
   cached_dev->refcount--;
   /* free the device and remove it from the cache */
@@ -180,7 +428,7 @@ static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev)
     list_del(&cached_dev->list);
 
     if (cached_dev->device) {
-      (*(cached_dev->device))->Release(cached_dev->device);
+      (*cached_dev->device)->Release(cached_dev->device);
       cached_dev->device = NULL;
     }
     IOObjectRelease (cached_dev->service);
@@ -300,12 +548,12 @@ static bool get_ioregistry_value_data (io_service_t service, CFStringRef propert
   return success;
 }
 
-static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io_service_t service)
+static int darwin_device_from_service (struct libusb_context *ctx, io_service_t service, usb_device_t* device)
 {
   io_cf_plugin_ref_t *plugInInterface = NULL;
-  usb_device_t **device;
   IOReturn kresult;
   SInt32 score;
+  
   const int max_retries = 5;
 
   /* The IOCreatePlugInInterfaceForService function might consistently return
@@ -325,17 +573,21 @@ static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io
     nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 1000}, NULL);
   }
 
-  if (kIOReturnSuccess != kresult || !plugInInterface) {
+  if (kIOReturnSuccess != kresult) {
     usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult));
-    return NULL;
+    return darwin_to_libusb(kresult);
+  }
+  if (!plugInInterface) {
+    usbi_dbg (ctx, "could not set up plugin for service");
+    return LIBUSB_ERROR_OTHER;
   }
 
-  (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(DeviceInterfaceID),
-                                           (LPVOID)&device);
+  (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(get_device_interface_id()),
+                                           (LPVOID)device);
   /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */
   (*plugInInterface)->Release (plugInInterface);
 
-  return device;
+  return LIBUSB_SUCCESS;
 }
 
 static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) {
@@ -377,8 +629,6 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
   struct darwin_cached_device *old_device;
 
   io_service_t device;
-  UInt64 session, locationID;
-  int ret;
 
   usbi_mutex_lock(&active_contexts_lock);
 
@@ -386,7 +636,9 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
     bool is_reenumerating = false;
 
     /* get the location from the i/o registry */
-    ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session);
+    UInt64 session = 0;
+    bool ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session);
+    UInt32 locationID = 0;
     (void) get_ioregistry_value_number (device, CFSTR("locationID"), kCFNumberSInt32Type, &locationID);
     IOObjectRelease (device);
     if (!ret)
@@ -394,18 +646,18 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
 
     /* we need to match darwin_ref_cached_device call made in darwin_get_cached_device function
        otherwise no cached device will ever get freed */
-    usbi_mutex_lock(&darwin_cached_devices_lock);
+    usbi_mutex_lock(&darwin_cached_devices_mutex);
     list_for_each_entry(old_device, &darwin_cached_devices, list, struct darwin_cached_device) {
       if (old_device->session == session) {
         if (old_device->in_reenumerate) {
           /* device is re-enumerating. do not dereference the device at this time. libusb_reset_device()
            * will deref if needed. */
-          usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 ", locationID: 0x%" PRIx64,
-                    session, locationID);
+          usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64
+                          ", locationID: 0x%" PRIx32, session, locationID);
 
           /* the device object is no longer usable so go ahead and release it */
           if (old_device->device) {
-            (*(old_device->device))->Release(old_device->device);
+            (*old_device->device)->Release(old_device->device);
             old_device->device = NULL;
           }
 
@@ -418,7 +670,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
       }
     }
 
-    usbi_mutex_unlock(&darwin_cached_devices_lock);
+    usbi_mutex_unlock(&darwin_cached_devices_mutex);
     if (is_reenumerating) {
       continue;
     }
@@ -466,8 +718,8 @@ static void darwin_fail_startup(void) {
 }
 
 static void *darwin_event_thread_main (void *arg0) {
+  UNUSED(arg0);
   IOReturn kresult;
-  struct libusb_context *ctx = (struct libusb_context *)arg0;
   CFRunLoopRef runloop;
   CFRunLoopSourceRef libusb_shutdown_cfsource;
   CFRunLoopSourceContext libusb_shutdown_cfsourcectx;
@@ -495,7 +747,7 @@ static void *darwin_event_thread_main (void *arg0) {
   io_iterator_t          libusb_add_device_iterator;
 
   /* ctx must only be used for logging during thread startup */
-  usbi_dbg (ctx, "creating hotplug event source");
+  usbi_dbg (NULL, "creating hotplug event source");
 
   runloop = CFRunLoopGetCurrent ();
   CFRetain (runloop);
@@ -519,7 +771,7 @@ static void *darwin_event_thread_main (void *arg0) {
                                               NULL, &libusb_rem_device_iterator);
 
   if (kresult != kIOReturnSuccess) {
-    usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
+    usbi_err (NULL, "could not add hotplug event source: %s", darwin_error_str (kresult));
     CFRelease (libusb_shutdown_cfsource);
     CFRelease (runloop);
     darwin_fail_startup ();
@@ -532,7 +784,7 @@ static void *darwin_event_thread_main (void *arg0) {
                                               NULL, &libusb_add_device_iterator);
 
   if (kresult != kIOReturnSuccess) {
-    usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
+    usbi_err (NULL, "could not add hotplug event source: %s", darwin_error_str (kresult));
     CFRelease (libusb_shutdown_cfsource);
     CFRelease (runloop);
     darwin_fail_startup ();
@@ -542,7 +794,7 @@ static void *darwin_event_thread_main (void *arg0) {
   darwin_clear_iterator (libusb_rem_device_iterator);
   darwin_clear_iterator (libusb_add_device_iterator);
 
-  usbi_dbg (ctx, "darwin event thread ready to receive events");
+  usbi_dbg (NULL, "darwin event thread ready to receive events");
 
   /* signal the main thread that the hotplug runloop has been created. */
   pthread_mutex_lock (&libusb_darwin_at_mutex);
@@ -582,73 +834,82 @@ static void *darwin_event_thread_main (void *arg0) {
   pthread_exit (NULL);
 }
 
-/* cleanup function to destroy cached devices */
+/* cleanup function to destroy cached devices. must be called with a lock on darwin_cached_devices_mutex */
 static void darwin_cleanup_devices(void) {
   struct darwin_cached_device *dev, *next;
 
   list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) {
+    if (dev->refcount > 1) {
+      usbi_err(NULL, "device still referenced at libusb_exit");
+    }
     darwin_deref_cached_device(dev);
   }
 }
 
-static int darwin_init(struct libusb_context *ctx) {
-  bool first_init;
-  int rc;
+/* must be called with a lock on darwin_cached_devices_mutex */
+static int darwin_first_time_init(void) {
+  if (NULL == darwin_cached_devices.next) {
+    list_init (&darwin_cached_devices);
+  }
 
-  first_init = (1 == ++init_count);
+  /* cache the interface versions that will be used. as a sanity check verify
+   * that the interface versions are non-zero. */
+  const struct darwin_iokit_interface *interface_interface = get_interface_interface();
+  const struct darwin_iokit_interface *device_interface = get_device_interface();
+  if (0 == interface_interface->version || 0 == device_interface->version) {
+    usbi_err(NULL, "could not determine the device or interface interface to use with this version "
+             "of macOS (or MacOS X), current_running_version = %" PRIu32, get_running_version());
+    return LIBUSB_ERROR_OTHER;
+  }
 
-  do {
-    if (first_init) {
-      if (NULL == darwin_cached_devices.next) {
-        list_init (&darwin_cached_devices);
-      }
-      assert(list_empty(&darwin_cached_devices));
-#if !defined(HAVE_CLOCK_GETTIME)
-      /* create the clocks that will be used if clock_gettime() is not available */
-      host_name_port_t host_self;
-
-      host_self = mach_host_self();
-      host_get_clock_service(host_self, CALENDAR_CLOCK, &clock_realtime);
-      host_get_clock_service(host_self, SYSTEM_CLOCK, &clock_monotonic);
-      mach_port_deallocate(mach_task_self(), host_self);
-#endif
-    }
+  if (!list_empty(&darwin_cached_devices)) {
+    usbi_err(NULL, "libusb_device reference not released on last exit. will not continue");
+    return LIBUSB_ERROR_OTHER;
+  }
 
-    rc = darwin_scan_devices (ctx);
-    if (LIBUSB_SUCCESS != rc)
-      break;
+  int rc = pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, NULL);
+  if (0 != rc) {
+    usbi_err (NULL, "could not create event thread, error %d", rc);
+    return LIBUSB_ERROR_OTHER;
+  }
 
-    if (first_init) {
-      rc = pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, ctx);
-      if (0 != rc) {
-        usbi_err (ctx, "could not create event thread, error %d", rc);
-        rc = LIBUSB_ERROR_OTHER;
-        break;
-      }
+  pthread_mutex_lock (&libusb_darwin_at_mutex);
+  libusb_darwin_at_started = true;
+  while (NULL == libusb_darwin_acfl) {
+    pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex);
+  }
 
-      pthread_mutex_lock (&libusb_darwin_at_mutex);
-      while (!libusb_darwin_acfl)
-        pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex);
-      if (libusb_darwin_acfl == LIBUSB_DARWIN_STARTUP_FAILURE) {
-        libusb_darwin_acfl = NULL;
-        rc = LIBUSB_ERROR_OTHER;
-      }
-      pthread_mutex_unlock (&libusb_darwin_at_mutex);
+  if (libusb_darwin_acfl == LIBUSB_DARWIN_STARTUP_FAILURE) {
+    libusb_darwin_acfl = NULL;
+    rc = LIBUSB_ERROR_OTHER;
+  }
+  pthread_mutex_unlock (&libusb_darwin_at_mutex);
 
-      if (0 != rc)
-        pthread_join (libusb_darwin_at, NULL);
+  return rc;
+}
+
+static int darwin_init_context(struct libusb_context *ctx) {
+  usbi_mutex_lock(&darwin_cached_devices_mutex);
+
+  bool first_init = (1 == ++init_count);
+
+  if (first_init) {
+    int rc = darwin_first_time_init();
+    if (LIBUSB_SUCCESS != rc) {
+      usbi_mutex_unlock(&darwin_cached_devices_mutex);
+      return rc;
     }
-  } while (0);
+  }
+  usbi_mutex_unlock(&darwin_cached_devices_mutex);
+
+  return darwin_scan_devices (ctx);
+}
 
+static int darwin_init(struct libusb_context *ctx) {
+  int rc = darwin_init_context(ctx);
   if (LIBUSB_SUCCESS != rc) {
-    if (first_init) {
-      darwin_cleanup_devices ();
-#if !defined(HAVE_CLOCK_GETTIME)
-      mach_port_deallocate(mach_task_self(), clock_realtime);
-      mach_port_deallocate(mach_task_self(), clock_monotonic);
-#endif
-    }
-    --init_count;
+    /* clean up any allocated resources */
+    darwin_exit(ctx);
   }
 
   return rc;
@@ -657,23 +918,26 @@ static int darwin_init(struct libusb_context *ctx) {
 static void darwin_exit (struct libusb_context *ctx) {
   UNUSED(ctx);
 
+  usbi_mutex_lock(&darwin_cached_devices_mutex);
   if (0 == --init_count) {
     /* stop the event runloop and wait for the thread to terminate. */
     pthread_mutex_lock (&libusb_darwin_at_mutex);
-    CFRunLoopSourceSignal (libusb_darwin_acfls);
-    CFRunLoopWakeUp (libusb_darwin_acfl);
-    while (libusb_darwin_acfl)
-      pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex);
+    if (NULL != libusb_darwin_acfls) {
+      CFRunLoopSourceSignal (libusb_darwin_acfls);
+      CFRunLoopWakeUp (libusb_darwin_acfl);
+      while (libusb_darwin_acfl)
+        pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex);
+    }
+
+    if (libusb_darwin_at_started) {
+      pthread_join (libusb_darwin_at, NULL);
+      libusb_darwin_at_started = false;
+    }
     pthread_mutex_unlock (&libusb_darwin_at_mutex);
-    pthread_join (libusb_darwin_at, NULL);
 
     darwin_cleanup_devices ();
-
-#if !defined(HAVE_CLOCK_GETTIME)
-    mach_port_deallocate(mach_task_self(), clock_realtime);
-    mach_port_deallocate(mach_task_self(), clock_monotonic);
-#endif
   }
+  usbi_mutex_unlock(&darwin_cached_devices_mutex);
 }
 
 static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) {
@@ -683,12 +947,12 @@ static int get_configuration_index (struct libusb_device *dev, UInt8 config_valu
   IOReturn kresult;
 
   /* is there a simpler way to determine the index? */
-  kresult = (*(priv->device))->GetNumberOfConfigurations (priv->device, &numConfig);
+  kresult = (*priv->device)->GetNumberOfConfigurations (priv->device, &numConfig);
   if (kresult != kIOReturnSuccess)
     return darwin_to_libusb (kresult);
 
   for (i = 0 ; i < numConfig ; i++) {
-    (*(priv->device))->GetConfigurationDescriptorPtr (priv->device, i, &desc);
+    (*priv->device)->GetConfigurationDescriptorPtr (priv->device, i, &desc);
 
     if (desc->bConfigurationValue == config_value)
       return i;
@@ -740,7 +1004,7 @@ static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t confi
 
 /* check whether the os has configured the device */
 static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, struct darwin_cached_device *dev) {
-  usb_device_t **darwin_device = dev->device;
+  usb_device_t darwin_device = dev->device;
 
   IOUSBConfigurationDescriptorPtr configDesc;
   IOUSBFindInterfaceRequest request;
@@ -776,7 +1040,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx,
   request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
   request.bAlternateSetting  = kIOUSBFindInterfaceDontCare;
 
-  kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator);
+  kresult = (*darwin_device)->CreateInterfaceIterator(darwin_device, &request, &interface_iterator);
   if (kresult != kIOReturnSuccess)
     return darwin_to_libusb (kresult);
 
@@ -805,7 +1069,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx,
   return LIBUSB_SUCCESS;
 }
 
-static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) {
+static IOReturn darwin_request_descriptor (usb_device_t device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) {
   IOUSBDevRequestTO req;
 
   assert(buffer_size <= UINT16_MAX);
@@ -826,7 +1090,7 @@ static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UI
 }
 
 static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *ctx, struct darwin_cached_device *dev) {
-  usb_device_t **device = dev->device;
+  usb_device_t device = dev->device;
   int retries = 1;
   long delay = 30000; // microseconds
   int unsuspended = 0, try_unsuspend = 1, try_reconfigure = 1;
@@ -877,15 +1141,17 @@ static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *
 
     if (kIOReturnSuccess != ret && is_open && try_unsuspend) {
       /* device may be suspended. unsuspend it and try again */
-#if DeviceVersion >= 320
-      UInt32 info = 0;
+#if MAX_DEVICE_VERSION >= 320
+      if (get_device_interface_version() >= 320) {
+        UInt32 info = 0;
 
-      /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */
-      (void)(*device)->GetUSBDeviceInformation (device, &info);
+        /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */
+        (void)(*IODEVICE_V(device, 320))->GetUSBDeviceInformation (device, &info);
 
-      /* note that the device was suspended */
-      if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info)
-        try_unsuspend = 1;
+        /* note that the device was suspended */
+        if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info)
+          try_unsuspend = 1;
+      }
 #endif
 
       if (try_unsuspend) {
@@ -998,7 +1264,7 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io
   UInt64 sessionID = 0, parent_sessionID = 0;
   UInt32 locationID = 0;
   enum libusb_error ret = LIBUSB_SUCCESS;
-  usb_device_t **device;
+  usb_device_t device;
   UInt8 port = 0;
 
   /* assuming sessionID != 0 normally (never seen it be 0) */
@@ -1018,10 +1284,10 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io
     usbi_dbg(ctx, "parent sessionID: 0x%" PRIx64, parent_sessionID);
   }
 
-  usbi_mutex_lock(&darwin_cached_devices_lock);
+  usbi_mutex_lock(&darwin_cached_devices_mutex);
   do {
     list_for_each_entry(new_device, &darwin_cached_devices, list, struct darwin_cached_device) {
-      usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x",
+      usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%" PRIx32 " against cached device with sessionID/locationID 0x%" PRIx64 "/0x%" PRIx32,
                sessionID, locationID, new_device->session, new_device->location);
       if (new_device->location == locationID && new_device->in_reenumerate) {
         usbi_dbg (ctx, "found cached device with matching location that is being re-enumerated");
@@ -1041,9 +1307,8 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io
 
     usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID);
 
-    device = darwin_device_from_service (ctx, service);
-    if (!device) {
-      ret = LIBUSB_ERROR_NO_DEVICE;
+    ret = darwin_device_from_service (ctx, service, &device);
+    if (LIBUSB_SUCCESS != ret) {
       break;
     }
 
@@ -1094,7 +1359,7 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io
     }
   } while (0);
 
-  usbi_mutex_unlock(&darwin_cached_devices_lock);
+  usbi_mutex_unlock(&darwin_cached_devices_mutex);
 
   return ret;
 }
@@ -1158,7 +1423,7 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct
       dev->parent_dev = usbi_get_device_by_session_id (ctx, (unsigned long) cached_device->parent_session);
     }
 
-    (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed);
+    (*priv->dev->device)->GetDeviceSpeed (priv->dev->device, &devSpeed);
 
     switch (devSpeed) {
     case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break;
@@ -1227,7 +1492,7 @@ static int darwin_open (struct libusb_device_handle *dev_handle) {
 
   if (0 == dpriv->open_count) {
     /* try to open the device */
-    kresult = (*(dpriv->device))->USBDeviceOpenSeize (dpriv->device);
+    kresult = (*dpriv->device)->USBDeviceOpenSeize (dpriv->device);
     if (kresult != kIOReturnSuccess) {
       usbi_warn (HANDLE_CTX (dev_handle), "USBDeviceOpen: %s", darwin_error_str(kresult));
 
@@ -1242,12 +1507,13 @@ static int darwin_open (struct libusb_device_handle *dev_handle) {
     }
 
     /* create async event source */
-    kresult = (*(dpriv->device))->CreateDeviceAsyncEventSource (dpriv->device, &priv->cfSource);
+    kresult = (*dpriv->device)->CreateDeviceAsyncEventSource (dpriv->device,
+                                                                                &priv->cfSource);
     if (kresult != kIOReturnSuccess) {
       usbi_err (HANDLE_CTX (dev_handle), "CreateDeviceAsyncEventSource: %s", darwin_error_str(kresult));
 
       if (priv->is_open) {
-        (*(dpriv->device))->USBDeviceClose (dpriv->device);
+        (*dpriv->device)->USBDeviceClose (dpriv->device);
       }
 
       priv->is_open = false;
@@ -1303,7 +1569,7 @@ static void darwin_close (struct libusb_device_handle *dev_handle) {
 
     if (priv->is_open) {
       /* close the device */
-      kresult = (*(dpriv->device))->USBDeviceClose(dpriv->device);
+      kresult = (*dpriv->device)->USBDeviceClose(dpriv->device);
       if (kresult != kIOReturnSuccess) {
         /* Log the fact that we had a problem closing the file, however failing a
          * close isn't really an error, so return success anyway */
@@ -1335,7 +1601,7 @@ static enum libusb_error darwin_set_configuration(struct libusb_device_handle *d
     if (dev_handle->claimed_interfaces & (1U << i))
       darwin_release_interface (dev_handle, i);
 
-  kresult = (*(dpriv->device))->SetConfiguration (dpriv->device, (UInt8)config);
+  kresult = (*dpriv->device)->SetConfiguration (dpriv->device, (UInt8)config);
   if (kresult != kIOReturnSuccess)
     return darwin_to_libusb (kresult);
 
@@ -1349,7 +1615,7 @@ static enum libusb_error darwin_set_configuration(struct libusb_device_handle *d
   return LIBUSB_SUCCESS;
 }
 
-static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_service_t *usbInterfacep) {
+static IOReturn darwin_get_interface (usb_device_t darwin_device, uint8_t ifc, io_service_t *usbInterfacep) {
   IOUSBFindInterfaceRequest request;
   IOReturn                  kresult;
   io_iterator_t             interface_iterator;
@@ -1364,7 +1630,7 @@ static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc,
   request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
   request.bAlternateSetting  = kIOUSBFindInterfaceDontCare;
 
-  kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator);
+  kresult = (*darwin_device)->CreateInterfaceIterator(darwin_device, &request, &interface_iterator);
   if (kresult != kIOReturnSuccess)
     return kresult;
 
@@ -1386,29 +1652,34 @@ static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc,
   return kIOReturnSuccess;
 }
 
+static const struct libusb_interface_descriptor *get_interface_descriptor_by_number(struct libusb_device_handle *dev_handle, struct libusb_config_descriptor *conf_desc, int iface, uint8_t altsetting)
+{
+  int i;
+
+  for (i = 0; i < conf_desc->bNumInterfaces; i++) {
+    if (altsetting < conf_desc->interface[i].num_altsetting && conf_desc->interface[i].altsetting[altsetting].bInterfaceNumber == iface) {
+      return &conf_desc->interface[i].altsetting[altsetting];
+    }
+  }
+
+  usbi_err(HANDLE_CTX(dev_handle), "interface %d with altsetting %d not found for device", iface, (int)altsetting);
+  return NULL;
+}
+
 static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, uint8_t iface) {
   struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle);
 
   /* current interface */
   struct darwin_interface *cInterface = &priv->interfaces[iface];
-#if InterfaceVersion >= 550
-  IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3};
-#else
-  UInt8 dont_care1, dont_care3;
-  UInt16 dont_care2;
-#endif
-
   IOReturn kresult;
-
-  UInt8 numep, direction, number;
+  uint8_t numep;
   int rc;
   struct libusb_context *ctx = HANDLE_CTX (dev_handle);
 
-
   usbi_dbg (ctx, "building table of endpoints.");
 
   /* retrieve the total number of endpoints on this interface */
-  kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep);
+  kresult = (*IOINTERFACE(cInterface))->GetNumEndpoints(IOINTERFACE(cInterface), &numep);
   if (kresult != kIOReturnSuccess) {
     usbi_err (ctx, "can't get number of endpoints for interface: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
@@ -1416,21 +1687,16 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle,
 
   /* iterate through pipe references */
   for (UInt8 i = 1 ; i <= numep ; i++) {
-#if InterfaceVersion >= 550
-    kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, i, &pipeProperties);
-    number = pipeProperties.bEndpointNumber;
-    direction = pipeProperties.bDirection;
-#else
-    kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1,
-                                                            &dont_care2, &dont_care3);
-#endif
+    darwin_pipe_properties_t pipe_properties;
+    kresult = darwin_get_pipe_properties(cInterface, i, &pipe_properties);
     if (kresult != kIOReturnSuccess) {
       /* probably a buggy device. try to get the endpoint address from the descriptors */
       struct libusb_config_descriptor *config;
+      const struct libusb_interface_descriptor *if_desc;
       const struct libusb_endpoint_descriptor *endpoint_desc;
       UInt8 alt_setting;
 
-      kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &alt_setting);
+      kresult = (*IOINTERFACE(cInterface))->GetAlternateSetting (IOINTERFACE(cInterface), &alt_setting);
       if (kresult != kIOReturnSuccess) {
         usbi_err (HANDLE_CTX (dev_handle), "can't get alternate setting for interface");
         return darwin_to_libusb (kresult);
@@ -1441,15 +1707,19 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle,
         return rc;
       }
 
-      if (iface >= config->bNumInterfaces) {
-        usbi_err (HANDLE_CTX (dev_handle), "interface %d out of range for device", iface);
+      if_desc = get_interface_descriptor_by_number (dev_handle, config, iface, alt_setting);
+      if (if_desc == NULL) {
+        libusb_free_config_descriptor (config);
         return LIBUSB_ERROR_NOT_FOUND;
       }
-      endpoint_desc = config->interface[iface].altsetting[alt_setting].endpoint + i - 1;
+
+      endpoint_desc = if_desc->endpoint + i - 1;
 
       cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress;
+      libusb_free_config_descriptor (config);
     } else {
-      cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK));
+      cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == pipe_properties.direction) << kUSBRqDirnShift) |
+                                                  (pipe_properties.number & LIBUSB_ENDPOINT_ADDRESS_MASK));
     }
 
     usbi_dbg (ctx, "interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift,
@@ -1521,18 +1791,22 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8
 
   /* Do the actual claim */
   kresult = (*plugInInterface)->QueryInterface(plugInInterface,
-                                               CFUUIDGetUUIDBytes(InterfaceInterfaceID),
-                                               (LPVOID)&cInterface->interface);
+                                               CFUUIDGetUUIDBytes(get_interface_interface_id()),
+                                               (LPVOID)&IOINTERFACE(cInterface));
   /* We no longer need the intermediate plug-in */
   /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */
   (*plugInInterface)->Release (plugInInterface);
-  if (kresult != kIOReturnSuccess || !cInterface->interface) {
+  if (kresult != kIOReturnSuccess) {
     usbi_err (ctx, "QueryInterface: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
   }
+  if (!IOINTERFACE(cInterface)) {
+    usbi_err (ctx, "QueryInterface: returned null interface");
+    return LIBUSB_ERROR_OTHER;
+  }
 
   /* claim the interface */
-  kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface);
+  kresult = (*IOINTERFACE(cInterface))->USBInterfaceOpen(IOINTERFACE(cInterface));
   if (kresult != kIOReturnSuccess) {
     usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
@@ -1550,7 +1824,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8
   cInterface->cfSource = NULL;
 
   /* create async event source */
-  kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource);
+  kresult = (*IOINTERFACE(cInterface))->CreateInterfaceAsyncEventSource (IOINTERFACE(cInterface), &cInterface->cfSource);
   if (kresult != kIOReturnSuccess) {
     usbi_err (ctx, "could not create async event source");
 
@@ -1576,8 +1850,9 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin
   struct darwin_interface *cInterface = &priv->interfaces[iface];
 
   /* Check to see if an interface is open */
-  if (!cInterface->interface)
+  if (!IOINTERFACE(cInterface)) {
     return LIBUSB_SUCCESS;
+  }
 
   /* clean up endpoint data */
   cInterface->num_endpoints = 0;
@@ -1589,15 +1864,15 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin
     cInterface->cfSource = NULL;
   }
 
-  kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface);
+  kresult = (*IOINTERFACE(cInterface))->USBInterfaceClose(IOINTERFACE(cInterface));
   if (kresult != kIOReturnSuccess)
     usbi_warn (HANDLE_CTX (dev_handle), "USBInterfaceClose: %s", darwin_error_str(kresult));
 
-  kresult = (*(cInterface->interface))->Release(cInterface->interface);
+  kresult = (*IOINTERFACE(cInterface))->Release(IOINTERFACE(cInterface));
   if (kresult != kIOReturnSuccess)
     usbi_warn (HANDLE_CTX (dev_handle), "Release: %s", darwin_error_str(kresult));
 
-  cInterface->interface = (usb_interface_t **) IO_OBJECT_NULL;
+  IOINTERFACE(cInterface) = NULL;
 
   return darwin_to_libusb (kresult);
 }
@@ -1607,7 +1882,7 @@ static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_han
   IOReturn kresult;
   uint8_t current_alt_setting;
 
-  kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &current_alt_setting);
+  kresult = (*IOINTERFACE(cInterface))->GetAlternateSetting (IOINTERFACE(cInterface), &current_alt_setting);
   if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) {
     return LIBUSB_ERROR_PIPE;
   }
@@ -1634,10 +1909,11 @@ static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_hand
   /* current interface */
   struct darwin_interface *cInterface = &priv->interfaces[iface];
 
-  if (!cInterface->interface)
+  if (!IOINTERFACE(cInterface)) {
     return LIBUSB_ERROR_NO_DEVICE;
+  }
 
-  kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting);
+  kresult = (*IOINTERFACE(cInterface))->SetAlternateInterface (IOINTERFACE(cInterface), altsetting);
   if (kresult == kIOReturnSuccess) {
     /* update the list of endpoints */
     ret = get_endpoints (dev_handle, iface);
@@ -1689,7 +1965,7 @@ static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned c
   }
 
   /* newer versions of darwin support clearing additional bits on the device's endpoint */
-  kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef);
+  kresult = (*IOINTERFACE(cInterface))->ClearPipeStallBothEnds(IOINTERFACE(cInterface), pipeRef);
   if (kresult != kIOReturnSuccess)
     usbi_warn (HANDLE_CTX (dev_handle), "ClearPipeStall: %s", darwin_error_str (kresult));
 
@@ -1783,12 +2059,12 @@ static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, b
   cached_configurations = alloca (sizeof (*cached_configurations) * descriptor.bNumConfigurations);
 
   for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) {
-    (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration);
+    (*dpriv->device)->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration);
     memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i]));
   }
 
   /* if we need to release capture */
-  if (HAS_CAPTURE_DEVICE()) {
+  if (get_running_version() >= 101000) {
     if (capture) {
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
       options |= kUSBReEnumerateCaptureDeviceMask;
@@ -1799,7 +2075,7 @@ static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, b
   }
 
   /* from macOS 10.11 ResetDevice no longer does anything so just use USBDeviceReEnumerate */
-  kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options);
+  kresult = (*dpriv->device)->USBDeviceReEnumerate (dpriv->device, options);
   if (kresult != kIOReturnSuccess) {
     usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult));
     dpriv->in_reenumerate = false;
@@ -1844,7 +2120,7 @@ static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, b
   }
 
   for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) {
-    (void) (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration);
+    (void) (*dpriv->device)->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration);
     if (memcmp (cached_configuration, cached_configurations + i, sizeof (cached_configurations[i]))) {
       usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i);
       return LIBUSB_ERROR_NOT_FOUND;
@@ -1864,7 +2140,7 @@ static int darwin_reset_device (struct libusb_device_handle *dev_handle) {
 #if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1
   if (dpriv->capture_count > 0) {
     /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */
-    kresult = (*(dpriv->device))->ResetDevice (dpriv->device);
+    kresult = (*dpriv->device)->ResetDevice (dpriv->device);
     ret = darwin_to_libusb (kresult);
   } else {
     ret = darwin_reenumerate_device (dev_handle, false);
@@ -1945,10 +2221,10 @@ static void darwin_destroy_device(struct libusb_device *dev) {
 
   if (dpriv->dev) {
     /* need to hold the lock in case this is the last reference to the device */
-    usbi_mutex_lock(&darwin_cached_devices_lock);
+    usbi_mutex_lock(&darwin_cached_devices_mutex);
     darwin_deref_cached_device (dpriv->dev);
     dpriv->dev = NULL;
-    usbi_mutex_unlock(&darwin_cached_devices_lock);
+    usbi_mutex_unlock(&darwin_cached_devices_mutex);
   }
 }
 
@@ -1956,17 +2232,10 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) {
   struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
   IOReturn               ret;
-  uint8_t                transferType;
   uint8_t                pipeRef;
-  uint16_t               maxPacketSize;
 
   struct darwin_interface *cInterface;
-#if InterfaceVersion >= 550
-  IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3};
-#else
-  /* None of the values below are used in libusb for bulk transfers */
-  uint8_t                 direction, number, interval;
-#endif
+  darwin_pipe_properties_t pipe_properties;
 
   if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) {
     usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface");
@@ -1974,47 +2243,38 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) {
     return LIBUSB_ERROR_NOT_FOUND;
   }
 
-#if InterfaceVersion >= 550
-  ret = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties);
-
-  transferType = pipeProperties.bTransferType;
-  maxPacketSize = pipeProperties.wMaxPacketSize;
-#else
-  ret = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number,
-                                                       &transferType, &maxPacketSize, &interval);
-#endif
-
-  if (ret) {
+  ret = darwin_get_pipe_properties(cInterface, pipeRef, &pipe_properties);
+  if (kIOReturnSuccess != ret) {
     usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out",
               darwin_error_str(ret), ret);
     return darwin_to_libusb (ret);
   }
 
-  if (0 != (transfer->length % maxPacketSize)) {
+  if (0 != (transfer->length % pipe_properties.max_packet_size)) {
     /* do not need a zero packet */
     transfer->flags &= ~LIBUSB_TRANSFER_ADD_ZERO_PACKET;
   }
 
   /* submit the request */
   /* timeouts are unavailable on interrupt endpoints */
-  if (transferType == kUSBInterrupt) {
+  if (pipe_properties.transfer_type == kUSBInterrupt) {
     if (IS_XFERIN(transfer))
-      ret = (*(cInterface->interface))->ReadPipeAsync(cInterface->interface, pipeRef, transfer->buffer,
-                                                      (UInt32)transfer->length, darwin_async_io_callback, itransfer);
+      ret = (*IOINTERFACE(cInterface))->ReadPipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer,
+                                                              (UInt32)transfer->length, darwin_async_io_callback, itransfer);
     else
-      ret = (*(cInterface->interface))->WritePipeAsync(cInterface->interface, pipeRef, transfer->buffer,
-                                                       (UInt32)transfer->length, darwin_async_io_callback, itransfer);
+      ret = (*IOINTERFACE(cInterface))->WritePipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer,
+                                                               (UInt32)transfer->length, darwin_async_io_callback, itransfer);
   } else {
     itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT;
 
     if (IS_XFERIN(transfer))
-      ret = (*(cInterface->interface))->ReadPipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer,
-                                                        (UInt32)transfer->length, transfer->timeout, transfer->timeout,
-                                                        darwin_async_io_callback, itransfer);
+      ret = (*IOINTERFACE(cInterface))->ReadPipeAsyncTO(IOINTERFACE(cInterface), pipeRef, transfer->buffer,
+                                                                (UInt32)transfer->length, transfer->timeout, transfer->timeout,
+                                                                darwin_async_io_callback, itransfer);
     else
-      ret = (*(cInterface->interface))->WritePipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer,
-                                                         (UInt32)transfer->length, transfer->timeout, transfer->timeout,
-                                                         darwin_async_io_callback, itransfer);
+      ret = (*IOINTERFACE(cInterface))->WritePipeAsyncTO(IOINTERFACE(cInterface), pipeRef, transfer->buffer,
+                                                                 (UInt32)transfer->length, transfer->timeout, transfer->timeout,
+                                                                 darwin_async_io_callback, itransfer);
   }
 
   if (ret)
@@ -2024,7 +2284,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) {
   return darwin_to_libusb (ret);
 }
 
-#if InterfaceVersion >= 550
+#if MAX_INTERFACE_VERSION >= 550
 static int submit_stream_transfer(struct usbi_transfer *itransfer) {
   struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
   struct darwin_interface *cInterface;
@@ -2037,16 +2297,22 @@ static int submit_stream_transfer(struct usbi_transfer *itransfer) {
     return LIBUSB_ERROR_NOT_FOUND;
   }
 
+  if (get_interface_interface_version() < 550) {
+    usbi_err (TRANSFER_CTX(transfer), "IOUSBFamily version %d does not support bulk stream transfers",
+              get_interface_interface_version());
+    return LIBUSB_ERROR_NOT_SUPPORTED;
+  }
+
   itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT;
 
   if (IS_XFERIN(transfer))
-    ret = (*(cInterface->interface))->ReadStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id,
-                                                             transfer->buffer, (UInt32)transfer->length, transfer->timeout,
-                                                             transfer->timeout, darwin_async_io_callback, itransfer);
+    ret = (*IOINTERFACE_V(cInterface, 550))->ReadStreamsPipeAsyncTO(IOINTERFACE(cInterface), pipeRef, itransfer->stream_id,
+                                                                  transfer->buffer, (UInt32)transfer->length, transfer->timeout,
+                                                                  transfer->timeout, darwin_async_io_callback, itransfer);
   else
-    ret = (*(cInterface->interface))->WriteStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id,
-                                                              transfer->buffer, (UInt32)transfer->length, transfer->timeout,
-                                                              transfer->timeout, darwin_async_io_callback, itransfer);
+    ret = (*IOINTERFACE_V(cInterface, 550))->WriteStreamsPipeAsyncTO(IOINTERFACE(cInterface), pipeRef, itransfer->stream_id,
+                                                                   transfer->buffer, (UInt32)transfer->length, transfer->timeout,
+                                                                   transfer->timeout, darwin_async_io_callback, itransfer);
 
   if (ret)
     usbi_err (TRANSFER_CTX (transfer), "bulk stream transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out",
@@ -2061,18 +2327,11 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) {
   struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
 
   IOReturn kresult;
-  uint8_t pipeRef, interval;
+  uint8_t pipeRef;
   UInt64 frame;
   AbsoluteTime atTime;
   int i;
-#if InterfaceVersion >= 550
-  IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3};
-#else
-  /* None of the values below are used in libusb for iso transfers */
-  uint8_t direction, number, transferType;
-  uint16_t maxPacketSize;
-#endif
-
+  darwin_pipe_properties_t pipe_properties;
   struct darwin_interface *cInterface;
 
   /* construct an array of IOUSBIsocFrames, reuse the old one if the sizes are the same */
@@ -2103,13 +2362,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) {
   }
 
   /* determine the properties of this endpoint and the speed of the device */
-#if InterfaceVersion >= 550
-  kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties);
-  interval = pipeProperties.bInterval;
-#else
-  kresult = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number,
-                                                 &transferType, &maxPacketSize, &interval);
-#endif
+  kresult = darwin_get_pipe_properties(cInterface, pipeRef, &pipe_properties);
   if (kresult != kIOReturnSuccess) {
     usbi_err (TRANSFER_CTX (transfer), "failed to get pipe properties: %d", kresult);
     free(tpriv->isoc_framelist);
@@ -2119,7 +2372,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) {
   }
 
   /* Last but not least we need the bus frame number */
-  kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime);
+  kresult = (*IOINTERFACE(cInterface))->GetBusFrameNumber(IOINTERFACE(cInterface), &frame, &atTime);
   if (kresult != kIOReturnSuccess) {
     usbi_err (TRANSFER_CTX (transfer), "failed to get bus frame number: %d", kresult);
     free(tpriv->isoc_framelist);
@@ -2136,20 +2389,20 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) {
 
   /* submit the request */
   if (IS_XFERIN(transfer))
-    kresult = (*(cInterface->interface))->ReadIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame,
-                                                             (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback,
-                                                             itransfer);
+    kresult = (*IOINTERFACE(cInterface))->ReadIsochPipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer, frame,
+                                                                     (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback,
+                                                                     itransfer);
   else
-    kresult = (*(cInterface->interface))->WriteIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame,
-                                                              (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback,
-                                                              itransfer);
+    kresult = (*IOINTERFACE(cInterface))->WriteIsochPipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer, frame,
+                                                                      (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback,
+                                                                      itransfer);
 
   if (LIBUSB_SPEED_FULL == transfer->dev_handle->dev->speed)
     /* Full speed */
-    cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1));
+    cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (pipe_properties.interval - 1));
   else
     /* High/super speed */
-    cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)) / 8;
+    cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (pipe_properties.interval - 1)) / 8;
 
   if (kresult != kIOReturnSuccess) {
     usbi_err (TRANSFER_CTX (transfer), "isochronous transfer failed (dir: %s): %s", IS_XFERIN(transfer) ? "In" : "Out",
@@ -2197,10 +2450,11 @@ static int submit_control_transfer(struct usbi_transfer *itransfer) {
       return LIBUSB_ERROR_NOT_FOUND;
     }
 
-    kresult = (*(cInterface->interface))->ControlRequestAsyncTO (cInterface->interface, pipeRef, &(tpriv->req), darwin_async_io_callback, itransfer);
+    kresult = (*IOINTERFACE(cInterface))->ControlRequestAsyncTO (IOINTERFACE(cInterface), pipeRef,
+                                                                         &(tpriv->req), darwin_async_io_callback, itransfer);
   } else
     /* control request on endpoint 0 */
-    kresult = (*(dpriv->device))->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer);
+    kresult = (*dpriv->device)->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer);
 
   if (kresult != kIOReturnSuccess)
     usbi_err (TRANSFER_CTX (transfer), "control request failed: %s", darwin_error_str(kresult));
@@ -2220,7 +2474,7 @@ static int darwin_submit_transfer(struct usbi_transfer *itransfer) {
   case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
     return submit_iso_transfer(itransfer);
   case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
-#if InterfaceVersion >= 550
+#if MAX_INTERFACE_VERSION >= 550
     return submit_stream_transfer(itransfer);
 #else
     usbi_err (TRANSFER_CTX(transfer), "IOUSBFamily version does not support bulk stream transfers");
@@ -2239,10 +2493,11 @@ static int cancel_control_transfer(struct usbi_transfer *itransfer) {
 
   usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions control pipe");
 
-  if (!dpriv->device)
+  if (!dpriv->device) {
     return LIBUSB_ERROR_NO_DEVICE;
+  }
 
-  kresult = (*(dpriv->device))->USBDeviceAbortPipeZero (dpriv->device);
+  kresult = (*dpriv->device)->USBDeviceAbortPipeZero (dpriv->device);
 
   return darwin_to_libusb (kresult);
 }
@@ -2262,23 +2517,29 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) {
     return LIBUSB_ERROR_NOT_FOUND;
   }
 
-  if (!dpriv->device)
+  if (!dpriv->device) {
     return LIBUSB_ERROR_NO_DEVICE;
+  }
 
   usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef);
 
   /* abort transactions */
-#if InterfaceVersion >= 550
-  if (LIBUSB_TRANSFER_TYPE_BULK_STREAM == transfer->type)
-    (*(cInterface->interface))->AbortStreamsPipe (cInterface->interface, pipeRef, itransfer->stream_id);
-  else
+#if MAX_INTERFACE_VERSION >= 550
+  if (LIBUSB_TRANSFER_TYPE_BULK_STREAM == transfer->type && get_interface_interface_version() >= 550) {
+    kresult = (*IOINTERFACE_V(cInterface, 550))->AbortStreamsPipe (IOINTERFACE(cInterface), pipeRef, itransfer->stream_id);
+  else
 #endif
-    (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef);
+  {
+    kresult = (*IOINTERFACE(cInterface))->AbortPipe (IOINTERFACE(cInterface), pipeRef);
+  }
 
-  usbi_dbg (ctx, "calling clear pipe stall to clear the data toggle bit");
 
-  /* newer versions of darwin support clearing additional bits on the device's endpoint */
-  kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef);
+  if (get_interface_interface_version() <= 245) {
+    /* with older releases of IOUSBFamily the OS always clears the host side data toggle. for
+       consistency also clear the data toggle on the device. */
+    usbi_dbg (ctx, "calling ClearPipeStallBothEnds to clear the data toggle bit");
+    kresult = (*IOINTERFACE(cInterface))->ClearPipeStallBothEnds(IOINTERFACE(cInterface), pipeRef);
+  }
 
   return darwin_to_libusb (kresult);
 }
@@ -2313,7 +2574,7 @@ static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0)
 
     (void) ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface);
 
-    (*(cInterface->interface))->WritePipe (cInterface->interface, pipeRef, transfer->buffer, 0);
+    (*IOINTERFACE(cInterface))->WritePipe (IOINTERFACE(cInterface), pipeRef, transfer->buffer, 0);
   }
 
   tpriv->result = result;
@@ -2389,29 +2650,38 @@ static int darwin_handle_transfer_completion (struct usbi_transfer *itransfer) {
   return usbi_handle_transfer_completion (itransfer, darwin_transfer_status (itransfer, tpriv->result));
 }
 
-#if !defined(HAVE_CLOCK_GETTIME)
 void usbi_get_monotonic_time(struct timespec *tp) {
-  mach_timespec_t sys_time;
+/* Check if the SDK is new enough to declare clock_gettime(), and the deployment target is at least 10.12. */
+#if ((MAC_OS_X_VERSION_MAX_ALLOWED >= 101200) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 101200))
+  clock_gettime(CLOCK_MONOTONIC, tp);
+#else
+  mach_timebase_info_data_t machTimeBaseInfo;
+  mach_timebase_info(&machTimeBaseInfo);
 
-  /* use system boot time as reference for the monotonic clock */
-  clock_get_time (clock_monotonic, &sys_time);
+  uint64_t uptime = mach_absolute_time();
+  uint64_t uptimeNano = uptime * machTimeBaseInfo.numer / machTimeBaseInfo.denom;
 
-  tp->tv_sec  = sys_time.tv_sec;
-  tp->tv_nsec = sys_time.tv_nsec;
+  uint64_t uptimeSeconds = uptimeNano / NSEC_PER_SEC;
+  uint64_t uptimeNanoRemainder = uptimeNano - (uptimeSeconds * NSEC_PER_SEC);
+
+  tp->tv_sec = uptimeSeconds;
+  tp->tv_nsec = uptimeNanoRemainder;
+#endif
 }
 
 void usbi_get_real_time(struct timespec *tp) {
-  mach_timespec_t sys_time;
-
-  /* CLOCK_REALTIME represents time since the epoch */
-  clock_get_time (clock_realtime, &sys_time);
-
-  tp->tv_sec  = sys_time.tv_sec;
-  tp->tv_nsec = sys_time.tv_nsec;
-}
+/* Check if the SDK is new enough to declare clock_gettime(), and the deployment target is at least 10.12. */
+#if ((MAC_OS_X_VERSION_MAX_ALLOWED >= 101200) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 101200))
+  clock_gettime(CLOCK_REALTIME, tp);
+#else
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  tp->tv_sec = tv.tv_sec;
+  tp->tv_nsec = tv.tv_usec * NSEC_PER_USEC;
 #endif
+}
 
-#if InterfaceVersion >= 550
+#if MAX_INTERFACE_VERSION >= 550
 static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32_t num_streams, unsigned char *endpoints,
                                  int num_endpoints) {
   struct darwin_interface *cInterface;
@@ -2425,7 +2695,7 @@ static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32
       return rc;
     }
 
-    (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams);
+    (*IOINTERFACE_V(cInterface, 550))->SupportsStreams (IOINTERFACE(cInterface), pipeRef, &supportsStreams);
     if (num_streams > supportsStreams)
       num_streams = supportsStreams;
   }
@@ -2438,7 +2708,7 @@ static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32
   for (i = 0 ; i < num_endpoints ; ++i) {
     (void) ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface);
 
-    rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, num_streams);
+    rc = (*IOINTERFACE_V(cInterface, 550))->CreateStreams (IOINTERFACE(cInterface), pipeRef, num_streams);
     if (kIOReturnSuccess != rc)
       return darwin_to_libusb(rc);
   }
@@ -2457,11 +2727,11 @@ static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigne
     if (0 != (rc = ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface)))
       return rc;
 
-    (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams);
+    (*IOINTERFACE_V(cInterface, 550))->SupportsStreams (IOINTERFACE(cInterface), pipeRef, &supportsStreams);
     if (0 == supportsStreams)
       return LIBUSB_ERROR_INVALID_PARAM;
 
-    rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, 0);
+    rc = (*IOINTERFACE_V(cInterface, 550))->CreateStreams (IOINTERFACE(cInterface), pipeRef, 0);
     if (kIOReturnSuccess != rc)
       return darwin_to_libusb(rc);
   }
@@ -2470,7 +2740,7 @@ static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigne
 }
 #endif
 
-#if InterfaceVersion >= 700
+#if MAX_INTERFACE_VERSION >= 700
 
 /* macOS APIs for getting entitlement values */
 
@@ -2504,15 +2774,10 @@ static int darwin_reload_device (struct libusb_device_handle *dev_handle) {
   struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
   enum libusb_error err;
 
-  usbi_mutex_lock(&darwin_cached_devices_lock);
-  (*(dpriv->device))->Release(dpriv->device);
-  dpriv->device = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service);
-  if (!dpriv->device) {
-    err = LIBUSB_ERROR_NO_DEVICE;
-  } else {
-    err = LIBUSB_SUCCESS;
-  }
-  usbi_mutex_unlock(&darwin_cached_devices_lock);
+  usbi_mutex_lock(&darwin_cached_devices_mutex);
+  (*dpriv->device)->Release(dpriv->device);
+  err = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service, &dpriv->device);
+  usbi_mutex_unlock(&darwin_cached_devices_mutex);
 
   return err;
 }
@@ -2526,8 +2791,7 @@ static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle,
   enum libusb_error err;
   struct libusb_context *ctx = HANDLE_CTX (dev_handle);
 
-  if (HAS_CAPTURE_DEVICE()) {
-  } else {
+  if (get_interface_interface_version() < 700) {
     return LIBUSB_ERROR_NOT_SUPPORTED;
   }
 
@@ -2570,8 +2834,7 @@ static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle,
   UNUSED(interface);
   struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
 
-  if (HAS_CAPTURE_DEVICE()) {
-  } else {
+  if (get_interface_interface_version() < 700) {
     return LIBUSB_ERROR_NOT_SUPPORTED;
   }
 
@@ -2638,14 +2901,14 @@ const struct usbi_os_backend usbi_backend = {
         .clear_halt = darwin_clear_halt,
         .reset_device = darwin_reset_device,
 
-#if InterfaceVersion >= 550
+#if MAX_INTERFACE_VERSION >= 550
         .alloc_streams = darwin_alloc_streams,
         .free_streams = darwin_free_streams,
 #endif
 
         .kernel_driver_active = darwin_kernel_driver_active,
 
-#if InterfaceVersion >= 700
+#if MAX_INTERFACE_VERSION >= 700
         .detach_kernel_driver = darwin_detach_kernel_driver,
         .attach_kernel_driver = darwin_attach_kernel_driver,
         .claim_interface = darwin_capture_claim_interface,
index 7b72fffb89531109ee74b2331ec631b962ce265a..86646bfee1a5b00313fc9061ca8fa3bc521f78cc 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * darwin backend for libusb 1.0
- * Copyright © 2008-2019 Nathan Hjelm <hjelmn@users.sourceforge.net>
- * Copyright © 2019      Google LLC. All rights reserved.
+ * Copyright © 2008-2023 Nathan Hjelm <hjelmn@users.sourceforge.net>
+ * Copyright © 2019-2023 Google LLC. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 
 /* IOUSBInterfaceInferface */
 
-/* New in OS 10.12.0. */
-#if defined (kIOUSBInterfaceInterfaceID800)
-
-#define usb_interface_t IOUSBInterfaceInterface800
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID800
-#define InterfaceVersion 800
-
-/* New in OS 10.10.0. */
-#elif defined (kIOUSBInterfaceInterfaceID700)
-
-#define usb_interface_t IOUSBInterfaceInterface700
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700
-#define InterfaceVersion 700
-
-/* New in OS 10.9.0. */
-#elif defined (kIOUSBInterfaceInterfaceID650)
-
-#define usb_interface_t IOUSBInterfaceInterface650
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID650
-#define InterfaceVersion 650
-
-/* New in OS 10.8.2 but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBInterfaceInterfaceID550)
-
-#define usb_interface_t IOUSBInterfaceInterface550
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID550
-#define InterfaceVersion 550
-
-/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBInterfaceInterfaceID500)
-
-#define usb_interface_t IOUSBInterfaceInterface500
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500
-#define InterfaceVersion 500
-
-/* New in OS 10.5.0. */
-#elif defined (kIOUSBInterfaceInterfaceID300)
-
-#define usb_interface_t IOUSBInterfaceInterface300
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300
-#define InterfaceVersion 300
-
-/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBInterfaceInterfaceID245)
-
-#define usb_interface_t IOUSBInterfaceInterface245
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245
-#define InterfaceVersion 245
-
-/* New in OS 10.4.0. */
-#elif defined (kIOUSBInterfaceInterfaceID220)
-
-#define usb_interface_t IOUSBInterfaceInterface220
-#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220
-#define InterfaceVersion 220
-
+#if defined(kIOUSBInterfaceInterfaceID800)
+#define MAX_INTERFACE_VERSION 800
+#elif defined(kIOUSBInterfaceInterfaceID700)
+#define        MAX_INTERFACE_VERSION 700
+#elif defined(kIOUSBInterfaceInterfaceID650)
+#define MAX_INTERFACE_VERSION 650
+#elif defined(kIOUSBInterfaceInterfaceID550)
+#define MAX_INTERFACE_VERSION 550
+#elif defined(kIOUSBInterfaceInterfaceID245)
+#define MAX_INTERFACE_VERSION 245
 #else
-
-#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target"
-
+#define        MAX_INTERFACE_VERSION 220
 #endif
 
-/* IOUSBDeviceInterface */
-
-/* New in OS 10.9.0. */
-#if defined (kIOUSBDeviceInterfaceID650)
-
-#define usb_device_t    IOUSBDeviceInterface650
-#define DeviceInterfaceID kIOUSBDeviceInterfaceID650
-#define DeviceVersion 650
-
-/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBDeviceInterfaceID500)
-
-#define usb_device_t    IOUSBDeviceInterface500
-#define DeviceInterfaceID kIOUSBDeviceInterfaceID500
-#define DeviceVersion 500
-
-/* New in OS 10.5.4 but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBDeviceInterfaceID320)
-
-#define usb_device_t    IOUSBDeviceInterface320
-#define DeviceInterfaceID kIOUSBDeviceInterfaceID320
-#define DeviceVersion 320
-
-/* New in OS 10.5.0. */
-#elif defined (kIOUSBDeviceInterfaceID300)
-
-#define usb_device_t    IOUSBDeviceInterface300
-#define DeviceInterfaceID kIOUSBDeviceInterfaceID300
-#define DeviceVersion 300
+/* set to the minimum version and casted up as needed. */
+typedef IOUSBInterfaceInterface220 **usb_interface_t;
 
-/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBDeviceInterfaceID245)
+#define IOINTERFACE0(darwin_interface, version) ((IOUSBInterfaceInterface ## version **) (darwin_interface)->interface)
+#define IOINTERFACE_V(darwin_interface, version) IOINTERFACE0(darwin_interface, version)
+#define IOINTERFACE(darwin_interface) ((darwin_interface)->interface)
 
-#define usb_device_t    IOUSBDeviceInterface245
-#define DeviceInterfaceID kIOUSBDeviceInterfaceID245
-#define DeviceVersion 245
-
-/* New in OS 10.2.3 but can't test deployment target to that granularity, so round up. */
-#elif defined (kIOUSBDeviceInterfaceID197)
-
-#define usb_device_t    IOUSBDeviceInterface197
-#define DeviceInterfaceID kIOUSBDeviceInterfaceID197
-#define DeviceVersion 197
+/* IOUSBDeviceInterface */
 
+#if defined(kIOUSBDeviceInterfaceID650)
+#define MAX_DEVICE_VERSION 650
+#elif defined(kIOUSBDeviceInterfaceID500)
+#define        MAX_DEVICE_VERSION 500
+#elif defined(kIOUSBDeviceInterfaceID320)
+#define MAX_DEVICE_VERSION 320
+#elif defined(kIOUSBDeviceInterfaceID300)
+#define MAX_DEVICE_VERSION 300
+#elif defined(kIOUSBDeviceInterfaceID245)
+#define MAX_DEVICE_VERSION 245
 #else
+#define        MAX_DEVICE_VERSION 197
+#endif
 
-#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target"
+/* set to the minimum version and casted up as needed */
+typedef IOUSBDeviceInterface197 **usb_device_t;
 
-#endif
+#define IODEVICE0(darwin_device, version) ((IOUSBDeviceInterface ## version **)(darwin_device))
+#define IODEVICE_V(darwin_device, version) IODEVICE0(darwin_device, version)
 
 #if !defined(kIOUSBHostInterfaceClassName)
 #define kIOUSBHostInterfaceClassName "IOUSBHostInterface"
 #define IO_OBJECT_NULL ((io_object_t) 0)
 #endif
 
-/* Testing availability */
-#ifndef __has_builtin
-  #define __has_builtin(x) 0  // Compatibility with non-clang compilers.
-#endif
-#if __has_builtin(__builtin_available)
-  #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *)
-#else
-  #define HAS_CAPTURE_DEVICE() 0
-#endif
+/* returns the current macOS version in a format similar to the
+ * MAC_OS_X_VERSION_MIN_REQUIRED macro.
+ * Examples:
+ *   10.1.5 -> 100105
+ *   13.3.0 -> 130300
+ */
+uint32_t get_running_version(void);
 
 typedef IOCFPlugInInterface *io_cf_plugin_ref_t;
 typedef IONotificationPortRef io_notification_port_t;
@@ -182,7 +111,7 @@ struct darwin_cached_device {
   UInt64                session;
   USBDeviceAddress      address;
   char                  sys_path[21];
-  usb_device_t        **device;
+  usb_device_t          device;
   io_service_t          service;
   int                   open_count;
   UInt8                 first_config, active_config, port;
@@ -201,7 +130,7 @@ struct darwin_device_handle_priv {
   CFRunLoopSourceRef   cfSource;
 
   struct darwin_interface {
-    usb_interface_t    **interface;
+    usb_interface_t      interface;
     uint8_t              num_endpoints;
     CFRunLoopSourceRef   cfSource;
     uint64_t             frames[256];
index 715a2d551c25b6398872a3883a99f9fde7a82472..4056dae2e2a4c9e0fbbe6ce0b01ee8643244798e 100644 (file)
 #ifdef HAVE_TIMERFD
 #include <sys/timerfd.h>
 #endif
+
+#ifdef __EMSCRIPTEN__
+/* On Emscripten `pipe` does not conform to the spec and does not block
+ * until events are available, which makes it unusable for event system
+ * and often results in deadlocks when `pipe` is in a loop like it is
+ * in libusb.
+ *
+ * Therefore use a custom event system based on browser event emitters. */
+#include <emscripten.h>
+#include <emscripten/atomic.h>
+#include <emscripten/threading.h>
+
+EM_ASYNC_JS(void, em_libusb_wait_async, (const _Atomic int* ptr, int expected_value, int timeout), {
+       await Atomics.waitAsync(HEAP32, ptr >> 2, expected_value, timeout).value;
+});
+
+static void em_libusb_wait(const _Atomic int *ptr, int expected_value, int timeout)
+{
+       if (emscripten_is_main_runtime_thread()) {
+               em_libusb_wait_async(ptr, expected_value, timeout);
+       } else {
+               emscripten_atomic_wait_u32((int*)ptr, expected_value, 1000000LL * timeout);
+       }
+}
+#endif
 #include <unistd.h>
 
 #ifdef HAVE_EVENTFD
@@ -131,6 +156,10 @@ void usbi_signal_event(usbi_event_t *event)
        r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy));
        if (r != sizeof(dummy))
                usbi_warn(NULL, "event write failed");
+#ifdef __EMSCRIPTEN__
+       event->has_event = 1;
+       emscripten_atomic_notify(&event->has_event, EMSCRIPTEN_NOTIFY_ALL_WAITERS);
+#endif
 }
 
 void usbi_clear_event(usbi_event_t *event)
@@ -141,6 +170,9 @@ void usbi_clear_event(usbi_event_t *event)
        r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy));
        if (r != sizeof(dummy))
                usbi_warn(NULL, "event read failed");
+#ifdef __EMSCRIPTEN__
+       event->has_event = 0;
+#endif
 }
 
 #ifdef HAVE_TIMERFD
@@ -223,6 +255,14 @@ int usbi_wait_for_events(struct libusb_context *ctx,
        int internal_fds, num_ready;
 
        usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms);
+#ifdef __EMSCRIPTEN__
+       // Emscripten's poll doesn't actually block, so we need to use an out-of-band
+       // waiting signal.
+       em_libusb_wait(&ctx->event.has_event, 0, timeout_ms);
+       // Emscripten ignores timeout_ms, but set it to 0 for future-proofing in case
+       // they ever implement real poll.
+       timeout_ms = 0;
+#endif
        num_ready = poll(fds, nfds, timeout_ms);
        usbi_dbg(ctx, "poll() returned %d", num_ready);
        if (num_ready == 0) {
index d81b5c4df4ab40e75aaf5b037eabdb478c20d784..4bd7f0fa2723b66ea4bcd9445110124ed7cad07d 100644 (file)
@@ -36,6 +36,9 @@ typedef struct usbi_event {
 #else
 typedef struct usbi_event {
        int pipefd[2];
+#ifdef __EMSCRIPTEN__
+       _Atomic int has_event;
+#endif
 } usbi_event_t;
 #define USBI_EVENT_OS_HANDLE(e)        ((e)->pipefd[0])
 #define USBI_EVENT_POLL_EVENTS POLLIN
index 0e0e22134ebfcf6844cdb3006ab0545b35ef7b5f..0079fd598a516ae284e717c86939d06457018d0e 100644 (file)
@@ -32,8 +32,6 @@
 #elif defined(__NetBSD__)
 # include <lwp.h>
 #elif defined(__OpenBSD__)
-# define _BSD_SOURCE
-# include <sys/syscall.h>
 # include <unistd.h>
 #elif defined(__sun__)
 # include <sys/lwp.h>
@@ -109,9 +107,7 @@ unsigned int usbi_get_tid(void)
 #elif defined(__NetBSD__)
        tid = _lwp_self();
 #elif defined(__OpenBSD__)
-       /* The following only works with OpenBSD > 5.1 as it requires
-        * real thread support. For 5.1 and earlier, -1 is returned. */
-       tid = syscall(SYS_getthrid);
+       tid = getthrid();
 #elif defined(__sun__)
        tid = _lwp_self();
 #else
index 9445fa9e87ec2cf50084626cda7c0f2fe59bae5a..eb355034b198fa04178456f9146999b34b70419c 100644 (file)
@@ -28,7 +28,7 @@
  * <li> Download the latest \c strerror.c from:<br>
  *      https://raw.github.com/libusb/libusb/master/libusb/strerror.c </li>
  * <li> Open the file in an UTF-8 capable editor </li>
- * <li> Add the 2 letter <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">ISO 639-1</a>
+ * <li> Add the 2 letter <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">ISO 639-1</a>
  *      code for your locale at the end of \c usbi_locale_supported[]<br>
  *    Eg. for Chinese, you would add "zh" so that:
  *    \code... usbi_locale_supported[] = { "en", "nl", "fr" };\endcode
@@ -160,7 +160,7 @@ static const char * const (*usbi_error_strings)[LIBUSB_ERROR_COUNT] = &usbi_loca
  * If libusb_setlocale() is not called, all messages will be in English.
  *
  * The following functions return translatable strings: libusb_strerror().
- * Note that the libusb log messages controlled through libusb_set_debug()
+ * Note that the libusb log messages controlled through LIBUSB_OPTION_LOG_LEVEL
  * are not translated, they are always in English.
  *
  * For POSIX UTF-8 environments if you want libusb to follow the standard
@@ -169,9 +169,9 @@ static const char * const (*usbi_error_strings)[LIBUSB_ERROR_COUNT] = &usbi_loca
  *
  * \param locale locale-string in the form of lang[_country_region][.codeset]
  * or lang[-region], where lang is a 2 letter ISO 639-1 code
- * \returns LIBUSB_SUCCESS on success
- * \returns LIBUSB_ERROR_INVALID_PARAM if the locale doesn't meet the requirements
- * \returns LIBUSB_ERROR_NOT_FOUND if the requested language is not supported
+ * \returns \ref LIBUSB_SUCCESS on success
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the locale doesn't meet the requirements
+ * \returns \ref LIBUSB_ERROR_NOT_FOUND if the requested language is not supported
  * \returns a LIBUSB_ERROR code on other errors
  */
 
index 1fa1f0be5cfdff5bb6787d0ac17eb898395e96e5..146cce23c285858333ddd2806c4c6311e7a23637 100644 (file)
 
 static void LIBUSB_CALL sync_transfer_cb(struct libusb_transfer *transfer)
 {
+       usbi_dbg(TRANSFER_CTX(transfer), "actual_length=%d", transfer->actual_length);
+
        int *completed = transfer->user_data;
        *completed = 1;
-       usbi_dbg(TRANSFER_CTX(transfer), "actual_length=%d", transfer->actual_length);
-       /* caller interprets result and frees transfer */
+       /*
+        * Right after setting 'completed', another thread might free the transfer, so don't
+        * access it beyond this point. The instantiating thread (not necessarily the
+        * current one) interprets the result and frees the transfer.
+        */
 }
 
 static void sync_transfer_wait_for_completion(struct libusb_transfer *transfer)
@@ -85,12 +90,12 @@ static void sync_transfer_wait_for_completion(struct libusb_transfer *transfer)
  * before giving up due to no response being received. For an unlimited
  * timeout, use value 0.
  * \returns on success, the number of bytes actually transferred
- * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out
- * \returns LIBUSB_ERROR_PIPE if the control request was not supported by the
+ * \returns \ref LIBUSB_ERROR_TIMEOUT if the transfer timed out
+ * \returns \ref LIBUSB_ERROR_PIPE if the control request was not supported by the
  * device
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_BUSY if called from event handling context
- * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_BUSY if called from event handling context
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
  * the operating system and/or hardware can support (see \ref asynclimits)
  * \returns another LIBUSB_ERROR code on other failures
  */
@@ -260,14 +265,14 @@ static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
  * timeout, use value 0.
  *
  * \returns 0 on success (and populates <tt>transferred</tt>)
- * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates
+ * \returns \ref LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates
  * <tt>transferred</tt>)
- * \returns LIBUSB_ERROR_PIPE if the endpoint halted
- * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see
+ * \returns \ref LIBUSB_ERROR_PIPE if the endpoint halted
+ * \returns \ref LIBUSB_ERROR_OVERFLOW if the device offered more data, see
  * \ref libusb_packetoverflow
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_BUSY if called from event handling context
- * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_BUSY if called from event handling context
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
  * the operating system and/or hardware can support (see \ref asynclimits)
  * \returns another LIBUSB_ERROR code on other failures
  */
@@ -315,13 +320,13 @@ int API_EXPORTED libusb_bulk_transfer(libusb_device_handle *dev_handle,
  * timeout, use value 0.
  *
  * \returns 0 on success (and populates <tt>transferred</tt>)
- * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out
- * \returns LIBUSB_ERROR_PIPE if the endpoint halted
- * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see
+ * \returns \ref LIBUSB_ERROR_TIMEOUT if the transfer timed out
+ * \returns \ref LIBUSB_ERROR_PIPE if the endpoint halted
+ * \returns \ref LIBUSB_ERROR_OVERFLOW if the device offered more data, see
  * \ref libusb_packetoverflow
- * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
- * \returns LIBUSB_ERROR_BUSY if called from event handling context
- * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
+ * \returns \ref LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns \ref LIBUSB_ERROR_BUSY if called from event handling context
+ * \returns \ref LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than
  * the operating system and/or hardware can support (see \ref asynclimits)
  * \returns another LIBUSB_ERROR code on other error
  */
index fe95d84b60c59eb7e2a1dd6dbe40ca992fc854e6..ca9df20ff1114ddf44da229c670f614113a6eae3 100644 (file)
@@ -7,7 +7,7 @@
 #define LIBUSB_MINOR 0
 #endif
 #ifndef LIBUSB_MICRO
-#define LIBUSB_MICRO 26
+#define LIBUSB_MICRO 27
 #endif
 #ifndef LIBUSB_NANO
 #define LIBUSB_NANO 0
index dbd5d5f5b1db19641df10726f2e34bb919db2f92..a6165f33444dc0bc6b3553dad90b52963632492e 100644 (file)
@@ -1 +1 @@
-#define LIBUSB_NANO 11724
+#define LIBUSB_NANO 11882